summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Xaml
diff options
context:
space:
mode:
authorJason Smith <jason.smith@xamarin.com>2016-03-22 13:02:25 -0700
committerJason Smith <jason.smith@xamarin.com>2016-03-22 16:13:41 -0700
commit17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch)
treeb5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Xaml
downloadxamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz
xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2
xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip
Initial import
Diffstat (limited to 'Xamarin.Forms.Xaml')
-rw-r--r--Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs439
-rw-r--r--Xamarin.Forms.Xaml/CreateValuesVisitor.cs369
-rw-r--r--Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs187
-rw-r--r--Xamarin.Forms.Xaml/FillResourceDictionariesVisitor.cs127
-rw-r--r--Xamarin.Forms.Xaml/HydratationContext.cs22
-rw-r--r--Xamarin.Forms.Xaml/IDictionaryExtensions.cs14
-rw-r--r--Xamarin.Forms.Xaml/IExpressionParser.cs15
-rw-r--r--Xamarin.Forms.Xaml/MarkupExpressionParser.cs227
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensionParser.cs80
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/ArrayExtension.cs39
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs36
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/DynamicResourceExtension.cs27
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/NullExtension.cs12
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/ReferenceExtension.cs39
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/StaticExtension.cs50
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs44
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/TemplateBindingExtension.cs34
-rw-r--r--Xamarin.Forms.Xaml/MarkupExtensions/TypeExtension.cs26
-rw-r--r--Xamarin.Forms.Xaml/NamescopingVisitor.cs79
-rw-r--r--Xamarin.Forms.Xaml/Properties/AssemblyInfo.cs25
-rw-r--r--Xamarin.Forms.Xaml/RegisterXNamesVisitor.cs72
-rw-r--r--Xamarin.Forms.Xaml/TypeArgumentsParser.cs54
-rw-r--r--Xamarin.Forms.Xaml/TypeConversionExtensions.cs162
-rw-r--r--Xamarin.Forms.Xaml/ValueConverterProvider.cs13
-rw-r--r--Xamarin.Forms.Xaml/ViewExtensions.cs46
-rw-r--r--Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.csproj92
-rw-r--r--Xamarin.Forms.Xaml/XamlCompilationAttribute.cs41
-rw-r--r--Xamarin.Forms.Xaml/XamlLoader.cs208
-rw-r--r--Xamarin.Forms.Xaml/XamlNode.cs234
-rw-r--r--Xamarin.Forms.Xaml/XamlNodeVisitor.cs62
-rw-r--r--Xamarin.Forms.Xaml/XamlParser.cs349
-rw-r--r--Xamarin.Forms.Xaml/XamlServiceProvider.cs309
-rw-r--r--Xamarin.Forms.Xaml/XmlName.cs58
-rw-r--r--Xamarin.Forms.Xaml/XmlnsHelper.cs56
34 files changed, 3647 insertions, 0 deletions
diff --git a/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
new file mode 100644
index 00000000..e013a742
--- /dev/null
+++ b/Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
@@ -0,0 +1,439 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Xaml.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class ApplyPropertiesVisitor : IXamlNodeVisitor
+ {
+ public static readonly IList<XmlName> Skips = new List<XmlName>
+ {
+ XmlName.xKey,
+ XmlName.xTypeArguments,
+ XmlName.xArguments,
+ XmlName.xFactoryMethod,
+ XmlName.xName
+ };
+
+ public ApplyPropertiesVisitor(HydratationContext context, bool stopOnResourceDictionary = false)
+ {
+ Context = context;
+ StopOnResourceDictionary = stopOnResourceDictionary;
+ }
+
+ 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; }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ var parentElement = parentNode as IElementNode;
+ var value = Values[node];
+ var source = Values[parentNode];
+
+ XmlName propertyName;
+ if (TryGetPropertyName(node, parentNode, out propertyName))
+ {
+ if (Skips.Contains(propertyName))
+ return;
+ if (parentElement.SkipProperties.Contains(propertyName))
+ return;
+ if (parentElement.SkipPrefix(node.NamespaceResolver.LookupPrefix(propertyName.NamespaceURI)))
+ return;
+ if (propertyName.NamespaceURI == "http://schemas.openxmlformats.org/markup-compatibility/2006" &&
+ propertyName.LocalName == "Ignorable")
+ {
+ (parentNode.IgnorablePrefixes ?? (parentNode.IgnorablePrefixes = new List<string>())).AddRange(
+ (value as string).Split(','));
+ return;
+ }
+ SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
+ }
+ else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
+ {
+ // Collection element, implicit content, or implicit collection element.
+ var contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo());
+ if (contentProperty != null)
+ {
+ 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)
+ {
+ if (node.SkipPrefix(node.NamespaceResolver.LookupPrefix(node.NamespaceURI)))
+ return;
+
+ var value = Values[node];
+ var parentElement = parentNode as IElementNode;
+ var markupExtension = value as IMarkupExtension;
+ var valueProvider = value as IValueProvider;
+
+ if (markupExtension != null)
+ {
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ value = markupExtension.ProvideValue(serviceProvider);
+ }
+
+ if (valueProvider != null)
+ {
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ value = valueProvider.ProvideValue(serviceProvider);
+ }
+
+ XmlName propertyName;
+ if (TryGetPropertyName(node, parentNode, out propertyName))
+ {
+ if (Skips.Contains(propertyName))
+ return;
+ if (parentElement.SkipProperties.Contains(propertyName))
+ return;
+
+ var source = Values[parentNode];
+
+ if (propertyName == XmlName._CreateContent && source is ElementTemplate)
+ SetTemplate(source as ElementTemplate, node);
+ else
+ SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
+ }
+ else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
+ {
+ // Collection element, implicit content, or implicit collection element.
+ string contentProperty;
+ if (typeof (IEnumerable).GetTypeInfo().IsAssignableFrom(Context.Types[parentElement].GetTypeInfo()))
+ {
+ var source = Values[parentNode];
+ if (Context.Types[parentElement] != typeof (ResourceDictionary))
+ {
+ var addMethod =
+ Context.Types[parentElement].GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1);
+ addMethod.Invoke(source, new[] { value });
+ }
+ }
+ else if ((contentProperty = GetContentPropertyName(Context.Types[parentElement].GetTypeInfo())) != null)
+ {
+ var name = new XmlName(node.NamespaceURI, contentProperty);
+ if (Skips.Contains(name))
+ return;
+ if (parentElement.SkipProperties.Contains(propertyName))
+ return;
+
+ var source = Values[parentNode];
+ SetPropertyValue(source, name, value, Context.RootElement, node, Context, node);
+ }
+ }
+ else if (IsCollectionItem(node, parentNode) && parentNode is ListNode)
+ {
+ var parentList = (ListNode)parentNode;
+ var source = Values[parentNode.Parent];
+
+ if (Skips.Contains(parentList.XmlName))
+ return;
+
+ var elementType = source.GetType();
+ var localname = parentList.XmlName.LocalName;
+
+ GetRealNameAndType(ref elementType, parentList.XmlName.NamespaceURI, ref localname, Context, node);
+
+ PropertyInfo propertyInfo = null;
+ 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;
+ }
+ }
+ if (propertyInfo == null)
+ throw new XamlParseException(string.Format("Property {0} not found", localname), node);
+ MethodInfo getter;
+ if (!propertyInfo.CanRead || (getter = propertyInfo.GetMethod) == null)
+ throw new XamlParseException(string.Format("Property {0} does not have an accessible getter", localname), node);
+ IEnumerable collection;
+ if ((collection = getter.Invoke(source, new object[] { }) as IEnumerable) == null)
+ throw new XamlParseException(string.Format("Property {0} is null or is not IEnumerable", localname), node);
+ MethodInfo addMethod;
+ if (
+ (addMethod =
+ collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) == null)
+ throw new XamlParseException(string.Format("Value of {0} does not have a Add() method", localname), node);
+
+ addMethod.Invoke(collection, new[] { Values[node] });
+ }
+ }
+
+ 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;
+ foreach (var kvp in parentElement.Properties)
+ {
+ if (kvp.Value != node)
+ continue;
+ name = kvp.Key;
+ return true;
+ }
+ return false;
+ }
+
+ static bool IsCollectionItem(INode node, INode parentNode)
+ {
+ var parentList = parentNode as IListNode;
+ if (parentList == null)
+ return false;
+ return parentList.CollectionItems.Contains(node);
+ }
+
+ internal static string GetContentPropertyName(TypeInfo typeInfo)
+ {
+ while (typeInfo != null)
+ {
+ var propName = GetContentPropertyName(typeInfo.CustomAttributes);
+ if (propName != null)
+ return propName;
+ typeInfo = typeInfo?.BaseType?.GetTypeInfo();
+ }
+ return null;
+ }
+
+ 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;
+ if (contentAttribute.ConstructorArguments[0].ArgumentType == typeof (string))
+ return (string)contentAttribute.ConstructorArguments[0].Value;
+ return null;
+ }
+
+ static bool GetRealNameAndType(ref Type elementType, string namespaceURI, ref string localname,
+ HydratationContext context, IXmlLineInfo lineInfo)
+ {
+ var dotIdx = localname.IndexOf('.');
+ if (dotIdx > 0)
+ {
+ 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);
+ if (xpe != null)
+ throw xpe;
+ return true;
+ }
+ return false;
+ }
+
+ static BindableProperty GetBindableProperty(Type elementType, string localName, IXmlLineInfo lineInfo,
+ bool throwOnError = false)
+ {
+ var bindableFieldInfo =
+ elementType.GetFields().FirstOrDefault(fi => fi.Name == localName + "Property" && fi.IsStatic && fi.IsPublic);
+
+ Exception exception = null;
+ if (exception == null && bindableFieldInfo == null)
+ {
+ exception =
+ new XamlParseException(
+ string.Format("BindableProperty {0} not found on {1}", localName + "Property", elementType.Name), lineInfo);
+ }
+
+ if (exception == null)
+ return bindableFieldInfo.GetValue(null) as BindableProperty;
+ if (throwOnError)
+ throw exception;
+ return null;
+ }
+
+ public static void SetPropertyValue(object xamlelement, XmlName propertyName, object value, BindableObject rootElement,
+ INode node, HydratationContext context, IXmlLineInfo lineInfo)
+ {
+ var elementType = xamlelement.GetType();
+ var localname = propertyName.LocalName;
+
+ var serviceProvider = new XamlServiceProvider(node, context);
+
+ //If it's an attached BP, update elementType and propertyName
+ var attached = GetRealNameAndType(ref elementType, propertyName.NamespaceURI, ref localname, context, lineInfo);
+
+ //If the target is an event, connect
+ var eventInfo = elementType.GetRuntimeEvent(localname);
+ if (eventInfo != null && value is string)
+ {
+ var methodInfo = rootElement.GetType().GetRuntimeMethods().FirstOrDefault(mi => mi.Name == (string)value);
+ if (methodInfo == null)
+ {
+ throw new XamlParseException(string.Format("No method {0} found on type {1}", value, rootElement.GetType()),
+ lineInfo);
+ }
+
+ try
+ {
+ eventInfo.AddEventHandler(xamlelement, methodInfo.CreateDelegate(eventInfo.EventHandlerType, rootElement));
+ }
+ catch (ArgumentException)
+ {
+ throw new XamlParseException(string.Format("Method {0} does not have the correct signature", value), lineInfo);
+ }
+
+ return;
+ }
+
+ var property = GetBindableProperty(elementType, localname, lineInfo, false);
+
+ //If Value is DynamicResource and it's a BP, SetDynamicResource
+ if (value is DynamicResource && property != null)
+ {
+ if (!(xamlelement.GetType()).GetTypeInfo().IsSubclassOf(typeof (BindableObject)))
+ throw new XamlParseException(string.Format("{0} is not a BindableObject", xamlelement.GetType().Name), lineInfo);
+ ((BindableObject)xamlelement).SetDynamicResource(property, ((DynamicResource)value).Key);
+ return;
+ }
+
+ //If value is BindingBase, and target is a BindableProperty, SetBinding
+ if (value is BindingBase && property != null)
+ {
+ if (!(xamlelement.GetType()).GetTypeInfo().IsSubclassOf(typeof (BindableObject)))
+ throw new XamlParseException(string.Format("{0} is not a BindableObject", xamlelement.GetType().Name), lineInfo);
+
+ ((BindableObject)xamlelement).SetBinding(property, value as BindingBase);
+ return;
+ }
+
+ //If it's a BindableProberty, SetValue
+ if (property != null)
+ {
+ if (!(xamlelement.GetType()).GetTypeInfo().IsSubclassOf(typeof (BindableObject)))
+ throw new XamlParseException(string.Format("{0} is not a BindableObject", xamlelement.GetType().Name), lineInfo);
+ Func<MemberInfo> minforetriever;
+ if (attached)
+ minforetriever = () => elementType.GetRuntimeMethod("Get" + localname, new[] { typeof (BindableObject) });
+ else
+ minforetriever = () => elementType.GetRuntimeProperty(localname);
+
+ var convertedValue = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider);
+
+ //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 &&
+ property.ReturnTypeInfo.GetGenericTypeDefinition() == typeof (Nullable<>);
+ if ((convertedValue == null && (!property.ReturnTypeInfo.IsValueType || nullable)) ||
+ (property.ReturnType.IsInstanceOfType(convertedValue)))
+ {
+ ((BindableObject)xamlelement).SetValue(property, convertedValue);
+ return;
+ }
+ }
+
+ var exception = new XamlParseException(
+ String.Format("No Property of name {0} found", propertyName.LocalName), lineInfo);
+
+ //If we can assign that value to a normal property, let's do it
+ var propertyInfo = elementType.GetRuntimeProperties().FirstOrDefault(p => p.Name == localname);
+ MethodInfo setter;
+ if (propertyInfo != null && propertyInfo.CanWrite && (setter = propertyInfo.SetMethod) != null)
+ {
+ object convertedValue = value.ConvertTo(propertyInfo.PropertyType, () => propertyInfo, serviceProvider);
+ if (convertedValue == null || propertyInfo.PropertyType.IsInstanceOfType(convertedValue))
+ {
+ try
+ {
+ setter.Invoke(xamlelement, new[] { convertedValue });
+ return;
+ }
+ catch (ArgumentException)
+ {
+ }
+ }
+ else
+ {
+ exception = new XamlParseException(
+ String.Format("Cannot assign property \"{0}\": type mismatch between \"{1}\" and \"{2}\"", propertyName.LocalName,
+ value.GetType(), propertyInfo.PropertyType), lineInfo);
+ }
+ }
+
+ //If it's an already initialized property, add to it
+ MethodInfo getter;
+ if (propertyInfo != null && propertyInfo.CanRead && (getter = propertyInfo.GetMethod) != null)
+ {
+ IEnumerable collection;
+ MethodInfo addMethod;
+ if ((collection = getter.Invoke(xamlelement, new object[] { }) as IEnumerable) != null
+ &&
+ (addMethod =
+ collection.GetType().GetRuntimeMethods().First(mi => mi.Name == "Add" && mi.GetParameters().Length == 1)) !=
+ null)
+ {
+ addMethod.Invoke(collection,
+ new[] { value.ConvertTo(propertyInfo.PropertyType, (Func<TypeConverter>)null, serviceProvider) });
+ return;
+ }
+ }
+
+ throw exception;
+ }
+
+ void SetTemplate(ElementTemplate dt, INode node)
+ {
+#pragma warning disable 0612
+ ((IDataTemplate)dt).LoadTemplate = () =>
+ {
+#pragma warning restore 0612
+ var context = new HydratationContext { ParentContext = Context, RootElement = Context.RootElement };
+ node.Accept(new ExpandMarkupsVisitor(context), null);
+ node.Accept(new NamescopingVisitor(context), null);
+ node.Accept(new CreateValuesVisitor(context), null);
+ node.Accept(new RegisterXNamesVisitor(context), null);
+ node.Accept(new FillResourceDictionariesVisitor(context), null);
+ node.Accept(new ApplyPropertiesVisitor(context, true), null);
+ return context.Values[node];
+ };
+ }
+ }
+} \ No newline at end of file
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
diff --git a/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs b/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs
new file mode 100644
index 00000000..0d5e9ee6
--- /dev/null
+++ b/Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs
@@ -0,0 +1,187 @@
+using System;
+using System.Collections.Generic;
+using System.Xml;
+using Xamarin.Forms.Xaml.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class ExpandMarkupsVisitor : IXamlNodeVisitor
+ {
+ public ExpandMarkupsVisitor(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 false; }
+ }
+
+ public bool StopOnResourceDictionary
+ {
+ get { return false; }
+ }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(MarkupNode markupnode, INode parentNode)
+ {
+ var parentElement = parentNode as IElementNode;
+ XmlName propertyName;
+ if (!ApplyPropertiesVisitor.TryGetPropertyName(markupnode, parentNode, out propertyName))
+ return;
+ if (ApplyPropertiesVisitor.Skips.Contains(propertyName))
+ return;
+ if (parentElement.SkipProperties.Contains(propertyName))
+ return;
+
+ var markupString = markupnode.MarkupString;
+ var node =
+ ParseExpression(ref markupString, markupnode.NamespaceResolver, markupnode, markupnode, parentNode) as IElementNode;
+ if (node != null)
+ {
+ ((IElementNode)parentNode).Properties[propertyName] = node;
+ node.Parent = parentNode;
+ }
+ }
+
+ public void Visit(ElementNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(RootNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(ListNode node, INode parentNode)
+ {
+ }
+
+ INode ParseExpression(ref string expression, IXmlNamespaceResolver nsResolver, IXmlLineInfo xmlLineInfo, INode node,
+ INode parentNode)
+ {
+ if (expression.StartsWith("{}", StringComparison.Ordinal))
+ return new ValueNode(expression.Substring(2), null);
+
+ if (expression[expression.Length - 1] != '}')
+ throw new Exception("Expression must end with '}'");
+
+ int len;
+ string match;
+ if (!MarkupExpressionParser.MatchMarkup(out match, expression, out len))
+ throw new Exception();
+ expression = expression.Substring(len).TrimStart();
+ if (expression.Length == 0)
+ throw new Exception("Expression did not end in '}'");
+
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ serviceProvider.Add(typeof (IXmlNamespaceResolver), nsResolver);
+
+ return new MarkupExpansionParser().Parse(match, ref expression, serviceProvider);
+ }
+
+ public class MarkupExpansionParser : MarkupExpressionParser, IExpressionParser<INode>
+ {
+ IElementNode node;
+
+ object IExpressionParser.Parse(string match, ref string remaining, IServiceProvider serviceProvider)
+ {
+ return Parse(match, ref remaining, serviceProvider);
+ }
+
+ public INode Parse(string match, ref string remaining, IServiceProvider serviceProvider)
+ {
+ var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver;
+ if (nsResolver == null)
+ throw new ArgumentException();
+ IXmlLineInfo xmlLineInfo = null;
+ var xmlLineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ if (xmlLineInfoProvider != null)
+ xmlLineInfo = xmlLineInfoProvider.XmlLineInfo;
+
+ var split = match.Split(':');
+ if (split.Length > 2)
+ throw new ArgumentException();
+
+ string prefix; //, name;
+ if (split.Length == 2)
+ {
+ prefix = split[0];
+ // name = split [1];
+ }
+ else
+ {
+ prefix = "";
+ // name = split [0];
+ }
+
+ Type type;
+ var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver;
+ if (typeResolver == null)
+ type = null;
+ else
+ {
+ //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix.
+ if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type))
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException(String.Format("MarkupExtension not found for {0}", match), lineInfo);
+ }
+ }
+
+ var namespaceuri = nsResolver.LookupNamespace(prefix) ?? "";
+ var xmltype = new XmlType(namespaceuri, type.Name, null);
+
+ if (type == null)
+ throw new NotSupportedException();
+
+ node = xmlLineInfo == null
+ ? new ElementNode(xmltype, null, nsResolver)
+ : new ElementNode(xmltype, null, nsResolver, xmlLineInfo.LineNumber, xmlLineInfo.LinePosition);
+
+ if (remaining.StartsWith("}", StringComparison.Ordinal))
+ {
+ remaining = remaining.Substring(1);
+ return node;
+ }
+
+ char next;
+ string piece;
+ while ((piece = GetNextPiece(ref remaining, out next)) != null)
+ HandleProperty(piece, serviceProvider, ref remaining, next != '=');
+
+ return node;
+ }
+
+ protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
+ {
+ var nsResolver = serviceProvider.GetService(typeof (IXmlNamespaceResolver)) as IXmlNamespaceResolver;
+
+ var childnode = value as INode ?? new ValueNode(strValue, nsResolver);
+ childnode.Parent = node;
+ if (prop != null)
+ {
+ var name = new XmlName(node.NamespaceURI, prop);
+ node.Properties[name] = childnode;
+ }
+ else //ContentProperty
+ node.CollectionItems.Add(childnode);
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/FillResourceDictionariesVisitor.cs b/Xamarin.Forms.Xaml/FillResourceDictionariesVisitor.cs
new file mode 100644
index 00000000..5eb47887
--- /dev/null
+++ b/Xamarin.Forms.Xaml/FillResourceDictionariesVisitor.cs
@@ -0,0 +1,127 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+using Xamarin.Forms.Xaml.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class FillResourceDictionariesVisitor : IXamlNodeVisitor
+ {
+ public FillResourceDictionariesVisitor(HydratationContext context)
+ {
+ Context = context;
+ }
+
+ HydratationContext Context { get; }
+
+ Dictionary<INode, object> Values
+ {
+ get { return Context.Values; }
+ }
+
+ public bool VisitChildrenFirst
+ {
+ get { return false; }
+ }
+
+ public bool StopOnDataTemplate
+ {
+ get { return true; }
+ }
+
+ public bool StopOnResourceDictionary
+ {
+ get { return false; }
+ }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(MarkupNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(ElementNode node, INode parentNode)
+ {
+ if (node.SkipPrefix(node.NamespaceResolver.LookupPrefix(node.NamespaceURI)))
+ return;
+
+ var value = Values[node];
+ var parentElement = parentNode as IElementNode;
+ var markupExtension = value as IMarkupExtension;
+ var valueProvider = value as IValueProvider;
+
+ //Set Resources in ResourcesDictionaries
+ if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
+ {
+ if (typeof (IEnumerable).GetTypeInfo().IsAssignableFrom(Context.Types[parentElement].GetTypeInfo()))
+ {
+ var source = Values[parentNode];
+ if (Context.Types[parentElement] == typeof (ResourceDictionary) && value is Style &&
+ !node.Properties.ContainsKey(XmlName.xKey))
+ {
+ node.Accept(new ApplyPropertiesVisitor(Context), parentNode);
+ if (markupExtension != null)
+ {
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ value = markupExtension.ProvideValue(serviceProvider);
+ }
+ if (valueProvider != null)
+ {
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ value = valueProvider.ProvideValue(serviceProvider);
+ }
+ ((ResourceDictionary)source).Add(value as Style);
+ }
+ else if (Context.Types[parentElement] == typeof (ResourceDictionary) && !node.Properties.ContainsKey(XmlName.xKey))
+ throw new XamlParseException("resources in ResourceDictionary require a x:Key attribute", node);
+ else if (Context.Types[parentElement] == typeof (ResourceDictionary) && node.Properties.ContainsKey(XmlName.xKey))
+ {
+ node.Accept(new ApplyPropertiesVisitor(Context), parentNode);
+ if (markupExtension != null)
+ {
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ value = markupExtension.ProvideValue(serviceProvider);
+ }
+ if (valueProvider != null)
+ {
+ var serviceProvider = new XamlServiceProvider(node, Context);
+ value = valueProvider.ProvideValue(serviceProvider);
+ }
+ ((ResourceDictionary)source).Add((string)(((ValueNode)node.Properties[XmlName.xKey]).Value), value);
+ }
+ }
+ }
+
+ //Set RD to VE
+ XmlName propertyName;
+ if (ApplyPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName))
+ {
+ if ((propertyName.LocalName == "Resources" ||
+ propertyName.LocalName.EndsWith(".Resources", StringComparison.Ordinal)) && value is ResourceDictionary)
+ {
+ var source = Values[parentNode];
+ ApplyPropertiesVisitor.SetPropertyValue(source, propertyName, value, Context.RootElement, node, Context, node);
+ }
+ }
+ }
+
+ public void Visit(RootNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(ListNode node, INode parentNode)
+ {
+ }
+
+ static bool IsCollectionItem(INode node, INode parentNode)
+ {
+ var parentList = parentNode as IListNode;
+ if (parentList == null)
+ return false;
+ return parentList.CollectionItems.Contains(node);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/HydratationContext.cs b/Xamarin.Forms.Xaml/HydratationContext.cs
new file mode 100644
index 00000000..befb9095
--- /dev/null
+++ b/Xamarin.Forms.Xaml/HydratationContext.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class HydratationContext
+ {
+ public HydratationContext()
+ {
+ Values = new Dictionary<INode, object>();
+ Types = new Dictionary<IElementNode, Type>();
+ }
+
+ public Dictionary<INode, object> Values { get; private set; }
+
+ public Dictionary<IElementNode, Type> Types { get; private set; }
+
+ public HydratationContext ParentContext { get; set; }
+
+ public BindableObject RootElement { get; set; }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/IDictionaryExtensions.cs b/Xamarin.Forms.Xaml/IDictionaryExtensions.cs
new file mode 100644
index 00000000..2cbb0376
--- /dev/null
+++ b/Xamarin.Forms.Xaml/IDictionaryExtensions.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal static class IDictionaryExtensions
+ {
+ public static void AddRange<TKey, TValue>(this IDictionary<TKey, TValue> dictionary,
+ IEnumerable<KeyValuePair<TKey, TValue>> collection)
+ {
+ foreach (var kvp in collection)
+ dictionary.Add(kvp);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/IExpressionParser.cs b/Xamarin.Forms.Xaml/IExpressionParser.cs
new file mode 100644
index 00000000..39b92273
--- /dev/null
+++ b/Xamarin.Forms.Xaml/IExpressionParser.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal interface IExpressionParser
+ {
+ object Parse(string match, ref string expression, IServiceProvider serviceProvider);
+ }
+
+ internal interface IExpressionParser<out T> : IExpressionParser
+ where T : class
+ {
+ new T Parse(string match, ref string expression, IServiceProvider serviceProvider);
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExpressionParser.cs b/Xamarin.Forms.Xaml/MarkupExpressionParser.cs
new file mode 100644
index 00000000..db97d320
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExpressionParser.cs
@@ -0,0 +1,227 @@
+//
+// MarkupExpressionParser.cs
+//
+// This code is partly salvaged from moonlight. Following licence apply.
+//
+//
+// Author(s):
+// Moonlight List (moonlight-list@lists.ximian.com)
+// Stephane Delcroix (stephane@mi8.be)
+//
+// Copyright 2009 Novell, Inc.
+// Copyright 2013 Xamarin, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Text;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal abstract class MarkupExpressionParser
+ {
+ public object ParseExpression(ref string expression, IServiceProvider serviceProvider)
+ {
+ if (serviceProvider == null)
+ throw new ArgumentNullException("serviceProvider");
+ if (expression.StartsWith("{}", StringComparison.Ordinal))
+ return expression.Substring(2);
+
+ if (expression[expression.Length - 1] != '}')
+ throw new Exception("Expression must end with '}'");
+
+ int len;
+ string match;
+ if (!MatchMarkup(out match, expression, out len))
+ return false;
+ expression = expression.Substring(len).TrimStart();
+ if (expression.Length == 0)
+ throw new Exception("Expression did not end in '}'");
+
+ var parser = Activator.CreateInstance(GetType()) as IExpressionParser;
+ return parser.Parse(match, ref expression, serviceProvider);
+ }
+
+ internal static bool MatchMarkup(out string match, string expression, out int end)
+ {
+ if (expression.Length < 2)
+ {
+ end = 1;
+ match = null;
+ return false;
+ }
+
+ if (expression[0] != '{')
+ {
+ end = 2;
+ match = null;
+ return false;
+ }
+
+ int i;
+ bool found = false;
+ for (i = 1; i < expression.Length; i++)
+ {
+ if (expression[i] == ' ')
+ continue;
+ found = true;
+ break;
+ }
+
+ if (!found)
+ {
+ end = 3;
+ match = null;
+ return false;
+ }
+
+ int c;
+ for (c = 0; c + i < expression.Length; c++)
+ {
+ if (expression[i + c] == ' ' || expression[i + c] == '}')
+ break;
+ }
+
+ if (i + c == expression.Length)
+ {
+ end = 6;
+ match = null;
+ return false;
+ }
+
+ end = i + c;
+ match = expression.Substring(i, c);
+ return true;
+ }
+
+ protected void HandleProperty(string prop, IServiceProvider serviceProvider, ref string remaining, bool isImplicit)
+ {
+ char next;
+ object value = null;
+ string str_value;
+
+ if (isImplicit)
+ {
+ SetPropertyValue(null, prop, null, serviceProvider);
+ return;
+ }
+ remaining = remaining.TrimStart();
+ if (remaining.StartsWith("{", StringComparison.Ordinal))
+ {
+ value = ParseExpression(ref remaining, serviceProvider);
+ remaining = remaining.TrimStart();
+
+ if (remaining.Length > 0 && remaining[0] == ',')
+ remaining = remaining.Substring(1);
+
+ str_value = value as string;
+ }
+ else
+ str_value = GetNextPiece(ref remaining, out next);
+
+ SetPropertyValue(prop, str_value, value, serviceProvider);
+ }
+
+ protected abstract void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider);
+
+ protected string GetNextPiece(ref string remaining, out char next)
+ {
+ bool inString = false;
+ int end = 0;
+ char stringTerminator = '\0';
+ remaining = remaining.TrimStart();
+ if (remaining.Length == 0)
+ {
+ next = Char.MaxValue;
+ return null;
+ }
+
+ var piece = new StringBuilder();
+ // If we're inside a quoted string we append all chars to our piece until we hit the ending quote.
+ while (end < remaining.Length &&
+ (inString || (remaining[end] != '}' && remaining[end] != ',' && remaining[end] != '=')))
+ {
+ if (inString)
+ {
+ if (remaining[end] == stringTerminator)
+ {
+ inString = false;
+ end ++;
+ break;
+ }
+ }
+ else
+ {
+ if (remaining[end] == '\'' || remaining[end] == '"')
+ {
+ inString = true;
+ stringTerminator = remaining[end];
+ end ++;
+ continue;
+ }
+ }
+
+ // If this is an escape char, consume it and append the next char to our piece.
+ if (remaining[end] == '\\')
+ {
+ end ++;
+ if (end == remaining.Length)
+ break;
+ }
+ piece.Append(remaining[end]);
+ end++;
+ }
+
+ if (inString && end == remaining.Length)
+ throw new Exception("Unterminated quoted string");
+
+ if (end == remaining.Length && !remaining.EndsWith("}", StringComparison.Ordinal))
+ throw new Exception("Expression did not end with '}'");
+
+ if (end == 0)
+ {
+ next = Char.MaxValue;
+ return null;
+ }
+
+ next = remaining[end];
+ remaining = remaining.Substring(end + 1);
+
+ // Whitespace is trimmed from the end of the piece before stripping
+ // quote chars from the start/end of the string.
+ while (piece.Length > 0 && char.IsWhiteSpace(piece[piece.Length - 1]))
+ piece.Length --;
+
+ if (piece.Length >= 2)
+ {
+ char first = piece[0];
+ char last = piece[piece.Length - 1];
+ if ((first == '\'' && last == '\'') || (first == '"' && last == '"'))
+ {
+ piece.Remove(piece.Length - 1, 1);
+ piece.Remove(0, 1);
+ }
+ }
+
+ return piece.ToString();
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensionParser.cs b/Xamarin.Forms.Xaml/MarkupExtensionParser.cs
new file mode 100644
index 00000000..cb62e1d1
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensionParser.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Reflection;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal sealed class MarkupExtensionParser : MarkupExpressionParser, IExpressionParser<object>
+ {
+ IMarkupExtension markupExtension;
+
+ public object Parse(string match, ref string remaining, IServiceProvider serviceProvider)
+ {
+ var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver;
+
+ //shortcut for Binding and StaticResource, to avoid too many reflection calls.
+ if (match == "Binding")
+ markupExtension = new BindingExtension();
+ else if (match == "TemplateBinding")
+ markupExtension = new TemplateBindingExtension();
+ else if (match == "StaticResource")
+ markupExtension = new StaticResourceExtension();
+ else
+ {
+ if (typeResolver == null)
+ return null;
+ Type type;
+
+ //The order of lookup is to look for the Extension-suffixed class name first and then look for the class name without the Extension suffix.
+ if (!typeResolver.TryResolve(match + "Extension", out type) && !typeResolver.TryResolve(match, out type))
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException(String.Format("MarkupExtension not found for {0}", match), lineInfo);
+ }
+ markupExtension = Activator.CreateInstance(type) as IMarkupExtension;
+ }
+
+ if (markupExtension == null)
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException(String.Format("Missing public default constructor for MarkupExtension {0}", match),
+ lineInfo);
+ }
+
+ char next;
+ if (remaining == "}")
+ return markupExtension.ProvideValue(serviceProvider);
+
+ string piece;
+ while ((piece = GetNextPiece(ref remaining, out next)) != null)
+ HandleProperty(piece, serviceProvider, ref remaining, next != '=');
+
+ return markupExtension.ProvideValue(serviceProvider);
+ }
+
+ protected override void SetPropertyValue(string prop, string strValue, object value, IServiceProvider serviceProvider)
+ {
+ MethodInfo setter;
+ if (prop == null)
+ {
+ //implicit property
+ var t = markupExtension.GetType();
+ prop = ApplyPropertiesVisitor.GetContentPropertyName(t.GetTypeInfo());
+ if (prop == null)
+ return;
+ setter = t.GetRuntimeProperty(prop).SetMethod;
+ }
+ else
+ setter = markupExtension.GetType().GetRuntimeProperty(prop).SetMethod;
+
+ if (value == null && strValue != null)
+ {
+ value = strValue.ConvertTo(markupExtension.GetType().GetRuntimeProperty(prop).PropertyType,
+ (Func<TypeConverter>)null, serviceProvider);
+ }
+
+ setter.Invoke(markupExtension, new[] { value });
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/ArrayExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/ArrayExtension.cs
new file mode 100644
index 00000000..9f594132
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/ArrayExtension.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Items")]
+ public class ArrayExtension : IMarkupExtension<Array>
+ {
+ public ArrayExtension()
+ {
+ Items = new List<object>();
+ }
+
+ public IList Items { get; }
+
+ public Type Type { get; set; }
+
+ public Array ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (Type == null)
+ throw new InvalidOperationException("Type argument mandatory for x:Array extension");
+
+ if (Items == null)
+ return null;
+
+ var array = Array.CreateInstance(Type, Items.Count);
+ for (var i = 0; i < Items.Count; i++)
+ ((IList)array)[i] = Items[i];
+
+ return array;
+ }
+
+ object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return (this as IMarkupExtension<Array>).ProvideValue(serviceProvider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs
new file mode 100644
index 00000000..f9af72a4
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/BindingExtension.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Path")]
+ public sealed class BindingExtension : IMarkupExtension<BindingBase>
+ {
+ public BindingExtension()
+ {
+ Mode = BindingMode.Default;
+ Path = Binding.SelfPath;
+ }
+
+ public string Path { get; set; }
+
+ public BindingMode Mode { get; set; }
+
+ public IValueConverter Converter { get; set; }
+
+ public object ConverterParameter { get; set; }
+
+ public string StringFormat { get; set; }
+
+ public object Source { get; set; }
+
+ BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return new Binding(Path, Mode, Converter, ConverterParameter, StringFormat, Source);
+ }
+
+ object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/DynamicResourceExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/DynamicResourceExtension.cs
new file mode 100644
index 00000000..a15b2ca4
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/DynamicResourceExtension.cs
@@ -0,0 +1,27 @@
+using System;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Key")]
+ public sealed class DynamicResourceExtension : IMarkupExtension<DynamicResource>
+ {
+ public string Key { get; set; }
+
+ public object ProvideValue(IServiceProvider serviceProvider)
+ {
+ return ((IMarkupExtension<DynamicResource>)this).ProvideValue(serviceProvider);
+ }
+
+ DynamicResource IMarkupExtension<DynamicResource>.ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (Key == null)
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException("DynamicResource markup require a Key", lineInfo);
+ }
+ return new DynamicResource(Key);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/NullExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/NullExtension.cs
new file mode 100644
index 00000000..7872a1c4
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/NullExtension.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ public class NullExtension : IMarkupExtension
+ {
+ public object ProvideValue(IServiceProvider serviceProvider)
+ {
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/ReferenceExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/ReferenceExtension.cs
new file mode 100644
index 00000000..6dc8280d
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/ReferenceExtension.cs
@@ -0,0 +1,39 @@
+using System;
+using Xamarin.Forms.Internals;
+using Xamarin.Forms.Xaml.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Name")]
+ public class ReferenceExtension : IMarkupExtension
+ {
+ public string Name { get; set; }
+
+ public object ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (serviceProvider == null)
+ throw new ArgumentNullException("serviceProvider");
+ var valueProvider = serviceProvider.GetService(typeof (IProvideValueTarget)) as IProvideParentValues;
+ if (valueProvider == null)
+ throw new ArgumentException("serviceProvider does not provide an IProvideValueTarget");
+ var namescopeprovider = serviceProvider.GetService(typeof (INameScopeProvider)) as INameScopeProvider;
+ if (namescopeprovider != null && namescopeprovider.NameScope != null)
+ {
+ var value = namescopeprovider.NameScope.FindByName(Name);
+ if (value != null)
+ return value;
+ }
+
+ foreach (var target in valueProvider.ParentObjects)
+ {
+ var ns = target as INameScope;
+ if (ns == null)
+ continue;
+ var value = ns.FindByName(Name);
+ if (value != null)
+ return value;
+ }
+ throw new Exception("Can't resolve name on Element");
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/StaticExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/StaticExtension.cs
new file mode 100644
index 00000000..7585eb6e
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/StaticExtension.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Member")]
+ public class StaticExtension : IMarkupExtension
+ {
+ public string Member { get; set; }
+
+ public object ProvideValue(IServiceProvider serviceProvider)
+ {
+ IXmlLineInfoProvider lineInfoProvider;
+ IXmlLineInfo lineInfo;
+
+ if (serviceProvider == null)
+ throw new ArgumentNullException("serviceProvider");
+ var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver;
+ if (typeResolver == null)
+ throw new ArgumentException("No IXamlTypeResolver in IServiceProvider");
+
+ if (string.IsNullOrEmpty(Member) || !Member.Contains("."))
+ {
+ lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException("Syntax for x:Static is [Member=][prefix:]typeName.staticMemberName", lineInfo);
+ }
+
+ var dotIdx = Member.LastIndexOf('.');
+ var typename = Member.Substring(0, dotIdx);
+ var membername = Member.Substring(dotIdx + 1);
+
+ var type = typeResolver.Resolve(typename, serviceProvider);
+
+ var pinfo = type.GetRuntimeProperties().FirstOrDefault(pi => pi.Name == membername && pi.GetMethod.IsStatic);
+ if (pinfo != null)
+ return pinfo.GetMethod.Invoke(null, new object[] { });
+
+ var finfo = type.GetRuntimeFields().FirstOrDefault(fi => fi.Name == membername && fi.IsStatic);
+ if (finfo != null)
+ return finfo.GetValue(null);
+
+ lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException(String.Format("No static member found for {0}", Member), lineInfo);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
new file mode 100644
index 00000000..e5a7aef7
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
@@ -0,0 +1,44 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Key")]
+ public sealed class StaticResourceExtension : IMarkupExtension
+ {
+ public string Key { get; set; }
+
+ public object ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (serviceProvider == null)
+ throw new ArgumentNullException("serviceProvider");
+ if (Key == null)
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ var lineInfo = (lineInfoProvider != null) ? lineInfoProvider.XmlLineInfo : new XmlLineInfo();
+ throw new XamlParseException("you must specify a key in {StaticResource}", lineInfo);
+ }
+ var valueProvider = serviceProvider.GetService(typeof (IProvideValueTarget)) as IProvideParentValues;
+ if (valueProvider == null)
+ throw new ArgumentException();
+ var xmlLineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ var xmlLineInfo = xmlLineInfoProvider != null ? xmlLineInfoProvider.XmlLineInfo : null;
+
+ foreach (var p in valueProvider.ParentObjects)
+ {
+ var ve = p as VisualElement;
+ if (ve == null)
+ continue;
+ if (ve.Resources == null)
+ continue;
+ object res;
+ if (ve.Resources.TryGetValue(Key, out res))
+ return res;
+ }
+ if (Application.Current != null && Application.Current.Resources != null &&
+ Application.Current.Resources.ContainsKey(Key))
+ return Application.Current.Resources[Key];
+
+ throw new XamlParseException(string.Format("StaticResource not found for key {0}", Key), xmlLineInfo);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/TemplateBindingExtension.cs
new file mode 100644
index 00000000..c14f9f94
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/TemplateBindingExtension.cs
@@ -0,0 +1,34 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("Path")]
+ public sealed class TemplateBindingExtension : IMarkupExtension<BindingBase>
+ {
+ public TemplateBindingExtension()
+ {
+ Mode = BindingMode.Default;
+ Path = Binding.SelfPath;
+ }
+
+ public string Path { get; set; }
+
+ public BindingMode Mode { get; set; }
+
+ public IValueConverter Converter { get; set; }
+
+ public object ConverterParameter { get; set; }
+
+ public string StringFormat { get; set; }
+
+ BindingBase IMarkupExtension<BindingBase>.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return new TemplateBinding(Path, Mode, Converter, ConverterParameter, StringFormat);
+ }
+
+ object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/MarkupExtensions/TypeExtension.cs b/Xamarin.Forms.Xaml/MarkupExtensions/TypeExtension.cs
new file mode 100644
index 00000000..f4bbf842
--- /dev/null
+++ b/Xamarin.Forms.Xaml/MarkupExtensions/TypeExtension.cs
@@ -0,0 +1,26 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ [ContentProperty("TypeName")]
+ public class TypeExtension : IMarkupExtension<Type>
+ {
+ public string TypeName { get; set; }
+
+ public Type ProvideValue(IServiceProvider serviceProvider)
+ {
+ if (serviceProvider == null)
+ throw new ArgumentNullException("serviceProvider");
+ var typeResolver = serviceProvider.GetService(typeof (IXamlTypeResolver)) as IXamlTypeResolver;
+ if (typeResolver == null)
+ throw new ArgumentException("No IXamlTypeResolver in IServiceProvider");
+
+ return typeResolver.Resolve(TypeName, serviceProvider);
+ }
+
+ object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
+ {
+ return (this as IMarkupExtension<Type>).ProvideValue(serviceProvider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/NamescopingVisitor.cs b/Xamarin.Forms.Xaml/NamescopingVisitor.cs
new file mode 100644
index 00000000..0651d045
--- /dev/null
+++ b/Xamarin.Forms.Xaml/NamescopingVisitor.cs
@@ -0,0 +1,79 @@
+using System.Collections.Generic;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class NamescopingVisitor : IXamlNodeVisitor
+ {
+ readonly Dictionary<INode, INameScope> scopes = new Dictionary<INode, INameScope>();
+
+ public NamescopingVisitor(HydratationContext context)
+ {
+ Values = context.Values;
+ }
+
+ Dictionary<INode, object> Values { get; set; }
+
+ public bool VisitChildrenFirst
+ {
+ get { return false; }
+ }
+
+ public bool StopOnDataTemplate
+ {
+ get { return false; }
+ }
+
+ public bool StopOnResourceDictionary
+ {
+ get { return false; }
+ }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ scopes[node] = scopes[parentNode];
+ }
+
+ public void Visit(MarkupNode node, INode parentNode)
+ {
+ scopes[node] = scopes[parentNode];
+ }
+
+ public void Visit(ElementNode node, INode parentNode)
+ {
+ var ns = parentNode == null || IsDataTemplate(node, parentNode) || IsStyle(node, parentNode)
+ ? new NameScope()
+ : scopes[parentNode];
+ node.Namescope = ns;
+ scopes[node] = ns;
+ }
+
+ public void Visit(RootNode node, INode parentNode)
+ {
+ var ns = new NameScope();
+ node.Namescope = ns;
+ scopes[node] = ns;
+ }
+
+ public void Visit(ListNode node, INode parentNode)
+ {
+ scopes[node] = scopes[parentNode];
+ }
+
+ static bool IsDataTemplate(INode node, INode parentNode)
+ {
+ var parentElement = parentNode as IElementNode;
+ INode createContent;
+ if (parentElement != null && parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) &&
+ createContent == node)
+ return true;
+ return false;
+ }
+
+ static bool IsStyle(INode node, INode parentNode)
+ {
+ var pnode = parentNode as ElementNode;
+ return pnode != null && pnode.XmlType.Name == "Style";
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/Properties/AssemblyInfo.cs b/Xamarin.Forms.Xaml/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000..03b4fb12
--- /dev/null
+++ b/Xamarin.Forms.Xaml/Properties/AssemblyInfo.cs
@@ -0,0 +1,25 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using Xamarin.Forms;
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("Xamarin.Forms.Xaml")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCulture("")]
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+//[assembly: AssemblyVersion ("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.UnitTests")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Build.Tasks")]
+[assembly: InternalsVisibleTo("Xamarin.Forms.Xaml.Design")]
+[assembly: Preserve] \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/RegisterXNamesVisitor.cs b/Xamarin.Forms.Xaml/RegisterXNamesVisitor.cs
new file mode 100644
index 00000000..7985cff1
--- /dev/null
+++ b/Xamarin.Forms.Xaml/RegisterXNamesVisitor.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class RegisterXNamesVisitor : IXamlNodeVisitor
+ {
+ public RegisterXNamesVisitor(HydratationContext context)
+ {
+ Values = context.Values;
+ }
+
+ Dictionary<INode, object> Values { get; }
+
+ public bool VisitChildrenFirst
+ {
+ get { return false; }
+ }
+
+ public bool StopOnDataTemplate
+ {
+ get { return true; }
+ }
+
+ public bool StopOnResourceDictionary
+ {
+ get { return false; }
+ }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ if (!IsXNameProperty(node, parentNode))
+ return;
+ try
+ {
+ ((IElementNode)parentNode).Namescope.RegisterName((string)node.Value, Values[parentNode]);
+ }
+ catch (ArgumentException ae)
+ {
+ if (ae.ParamName != "name")
+ throw ae;
+ throw new XamlParseException(
+ string.Format("An element with the name \"{0}\" already exists in this NameScope", (string)node.Value), node);
+ }
+ }
+
+ public void Visit(MarkupNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(ElementNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(RootNode node, INode parentNode)
+ {
+ }
+
+ public void Visit(ListNode node, INode parentNode)
+ {
+ }
+
+ static bool IsXNameProperty(ValueNode node, INode parentNode)
+ {
+ var parentElement = parentNode as IElementNode;
+ INode xNameNode;
+ if (parentElement != null && parentElement.Properties.TryGetValue(XmlName.xName, out xNameNode) && xNameNode == node)
+ return true;
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/TypeArgumentsParser.cs b/Xamarin.Forms.Xaml/TypeArgumentsParser.cs
new file mode 100644
index 00000000..59b2dd0a
--- /dev/null
+++ b/Xamarin.Forms.Xaml/TypeArgumentsParser.cs
@@ -0,0 +1,54 @@
+using System.Collections.Generic;
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal static class TypeArgumentsParser
+ {
+ public static IList<XmlType> ParseExpression(string expression, IXmlNamespaceResolver resolver, IXmlLineInfo lineInfo)
+ {
+ var typeList = new List<XmlType>();
+ while (!string.IsNullOrWhiteSpace(expression))
+ {
+ var match = expression;
+ typeList.Add(Parse(match, ref expression, resolver, lineInfo));
+ }
+ return typeList;
+ }
+
+ static XmlType Parse(string match, ref string remaining, IXmlNamespaceResolver resolver, IXmlLineInfo lineinfo)
+ {
+ remaining = null;
+ int parensCount = 0;
+ int pos = 0;
+ bool isGeneric = false;
+
+ for (pos = 0; pos < match.Length; pos++)
+ {
+ if (match[pos] == '(')
+ {
+ parensCount++;
+ isGeneric = true;
+ }
+ else if (match[pos] == ')')
+ parensCount--;
+ else if (match[pos] == ',' && parensCount == 0)
+ {
+ remaining = match.Substring(pos + 1);
+ break;
+ }
+ }
+ var type = match.Substring(0, pos).Trim();
+
+ IList<XmlType> typeArguments = null;
+ if (isGeneric)
+ {
+ typeArguments = ParseExpression(
+ type.Substring(type.IndexOf('(') + 1, type.LastIndexOf(')') - type.IndexOf('(') - 1), resolver, lineinfo);
+ type = type.Substring(0, type.IndexOf('('));
+ }
+ var namespaceuri = type.Contains(":") ? resolver.LookupNamespace(type.Split(':')[0].Trim()) : "";
+ return new XmlType(namespaceuri, type, typeArguments);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/TypeConversionExtensions.cs b/Xamarin.Forms.Xaml/TypeConversionExtensions.cs
new file mode 100644
index 00000000..a81ed11d
--- /dev/null
+++ b/Xamarin.Forms.Xaml/TypeConversionExtensions.cs
@@ -0,0 +1,162 @@
+//
+// InternalExtensions.cs
+//
+// Author:
+// Stephane Delcroix <stephane@mi8.be>
+//
+// Copyright (c) 2013 Mobile Inception
+// Copyright (c) 2014 Xamarin, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal static class TypeConversionExtensions
+ {
+ internal static object ConvertTo(this object value, Type toType, Func<ParameterInfo> pinfoRetriever,
+ IServiceProvider serviceProvider)
+ {
+ Func<TypeConverter> getConverter = () =>
+ {
+ ParameterInfo pInfo;
+ if (pinfoRetriever == null || (pInfo = pinfoRetriever()) == null)
+ return null;
+
+ var converterTypeName = pInfo.CustomAttributes.GetTypeConverterTypeName();
+ if (converterTypeName == null)
+ return null;
+ var convertertype = Type.GetType(converterTypeName);
+ return (TypeConverter)Activator.CreateInstance(convertertype);
+ };
+
+ return ConvertTo(value, toType, getConverter, serviceProvider);
+ }
+
+ internal static object ConvertTo(this object value, Type toType, Func<MemberInfo> minfoRetriever,
+ IServiceProvider serviceProvider)
+ {
+ Func<object> getConverter = () =>
+ {
+ MemberInfo memberInfo;
+
+ var converterTypeName = toType.GetTypeInfo().CustomAttributes.GetTypeConverterTypeName();
+ if (minfoRetriever != null && (memberInfo = minfoRetriever()) != null)
+ converterTypeName = memberInfo.CustomAttributes.GetTypeConverterTypeName() ?? converterTypeName;
+ if (converterTypeName == null)
+ return null;
+
+ var convertertype = Type.GetType(converterTypeName);
+ return Activator.CreateInstance(convertertype);
+ };
+
+ return ConvertTo(value, toType, getConverter, serviceProvider);
+ }
+
+ static string GetTypeConverterTypeName(this IEnumerable<CustomAttributeData> attributes)
+ {
+ var converterAttribute =
+ attributes.FirstOrDefault(cad => TypeConverterAttribute.TypeConvertersType.Contains(cad.AttributeType.FullName));
+ if (converterAttribute == null)
+ return null;
+ if (converterAttribute.ConstructorArguments[0].ArgumentType == typeof (string))
+ return (string)converterAttribute.ConstructorArguments[0].Value;
+ if (converterAttribute.ConstructorArguments[0].ArgumentType == typeof (Type))
+ return ((Type)converterAttribute.ConstructorArguments[0].Value).AssemblyQualifiedName;
+ return null;
+ }
+
+ //Don't change the name or the signature of this, it's used by XamlC
+ public static object ConvertTo(this object value, Type toType, Type convertertype, IServiceProvider serviceProvider)
+ {
+ if (convertertype == null)
+ return value.ConvertTo(toType, (Func<object>)null, serviceProvider);
+ Func<object> getConverter = () => Activator.CreateInstance(convertertype);
+ ;
+ return value.ConvertTo(toType, getConverter, serviceProvider);
+ }
+
+ internal static object ConvertTo(this object value, Type toType, Func<object> getConverter,
+ IServiceProvider serviceProvider)
+ {
+ if (value == null)
+ return null;
+
+ var str = value as string;
+ if (str != null)
+ {
+ //If there's a [TypeConverter], use it
+ object converter = getConverter?.Invoke();
+ var xfTypeConverter = converter as TypeConverter;
+ var xfExtendedTypeConverter = xfTypeConverter as IExtendedTypeConverter;
+ if (xfExtendedTypeConverter != null)
+ return value = xfExtendedTypeConverter.ConvertFromInvariantString(str, serviceProvider);
+ if (xfTypeConverter != null)
+ return value = xfTypeConverter.ConvertFromInvariantString(str);
+ var converterType = converter?.GetType();
+ if (converterType != null)
+ {
+ var convertFromStringInvariant = converterType.GetRuntimeMethod("ConvertFromInvariantString",
+ new[] { typeof (string) });
+ if (convertFromStringInvariant != null)
+ return value = convertFromStringInvariant.Invoke(converter, new object[] { str });
+ }
+
+ //If the type is nullable, as the value is not null, it's safe to assume we want the built-in conversion
+ if (toType.GetTypeInfo().IsGenericType && toType.GetGenericTypeDefinition() == typeof (Nullable<>))
+ toType = Nullable.GetUnderlyingType(toType);
+
+ //Obvious Built-in conversions
+ if (toType.GetTypeInfo().IsEnum)
+ return Enum.Parse(toType, str);
+ //TODO supports Int16, 64, Byte, Char, ...
+ if (toType == typeof (Int32))
+ return Int32.Parse(str, CultureInfo.InvariantCulture);
+ if (toType == typeof (float))
+ return Single.Parse(str, CultureInfo.InvariantCulture);
+ if (toType == typeof (double))
+ return Double.Parse(str, CultureInfo.InvariantCulture);
+ if (toType == typeof (bool))
+ return Boolean.Parse(str);
+ if (toType == typeof (TimeSpan))
+ return TimeSpan.Parse(str, CultureInfo.InvariantCulture);
+ if (toType == typeof (DateTime))
+ return DateTime.Parse(str, CultureInfo.InvariantCulture);
+ if (toType == typeof (string) && str.StartsWith("{}", StringComparison.Ordinal))
+ return str.Substring(2);
+ if (toType == typeof (string))
+ return value;
+ }
+
+ //if there's an implicit conversion, convert
+ if (value != null)
+ {
+ var cast = value.GetType().GetRuntimeMethod("op_Implicit", new[] { value.GetType() });
+ if (cast != null && cast.ReturnType == toType)
+ value = cast.Invoke(null, new[] { value });
+ }
+ return value;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/ValueConverterProvider.cs b/Xamarin.Forms.Xaml/ValueConverterProvider.cs
new file mode 100644
index 00000000..f7859e0c
--- /dev/null
+++ b/Xamarin.Forms.Xaml/ValueConverterProvider.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Reflection;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal class ValueConverterProvider : IValueConverterProvider
+ {
+ public object Convert(object value, Type toType, Func<MemberInfo> minfoRetriever, IServiceProvider serviceProvider)
+ {
+ return value.ConvertTo(toType, minfoRetriever, serviceProvider);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/ViewExtensions.cs b/Xamarin.Forms.Xaml/ViewExtensions.cs
new file mode 100644
index 00000000..fb8a612a
--- /dev/null
+++ b/Xamarin.Forms.Xaml/ViewExtensions.cs
@@ -0,0 +1,46 @@
+//
+// ViewExtensions.cs
+//
+// Author:
+// Stephane Delcroix <stephane@mi8.be>
+//
+// Copyright (c) 2013 Mobile Inception
+// Copyright (c) 2013 Xamarin, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ public static class Extensions
+ {
+ public static TView LoadFromXaml<TView>(this TView view, Type callingType) where TView : BindableObject
+ {
+ XamlLoader.Load(view, callingType);
+ return view;
+ }
+
+ internal static TView LoadFromXaml<TView>(this TView view, string xaml) where TView : BindableObject
+ {
+ XamlLoader.Load(view, xaml);
+ return view;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.csproj b/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.csproj
new file mode 100644
index 00000000..9c60ec98
--- /dev/null
+++ b/Xamarin.Forms.Xaml/Xamarin.Forms.Xaml.csproj
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>12.0.0</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{9DB2F292-8034-4E06-89AD-98BBDA4306B9}</ProjectGuid>
+ <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <OutputType>Library</OutputType>
+ <RootNamespace>Xamarin.Forms.Xaml</RootNamespace>
+ <AssemblyName>Xamarin.Forms.Xaml</AssemblyName>
+ <TargetFrameworkProfile>Profile259</TargetFrameworkProfile>
+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <MinimumVisualStudioVersion>10.0</MinimumVisualStudioVersion>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>full</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release</OutputPath>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <ConsolePause>false</ConsolePause>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Turkey|AnyCPU'">
+ <DebugSymbols>true</DebugSymbols>
+ <OutputPath>bin\Turkey\</OutputPath>
+ <DefineConstants>DEBUG;</DefineConstants>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ <DebugType>full</DebugType>
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <ErrorReport>prompt</ErrorReport>
+ <CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
+ </PropertyGroup>
+ <ItemGroup>
+ <Compile Include="..\Xamarin.Forms.Core\Properties\GlobalAssemblyInfo.cs">
+ <Link>Properties\GlobalAssemblyInfo.cs</Link>
+ </Compile>
+ <Compile Include="MarkupExtensions\TemplateBindingExtension.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="MarkupExpressionParser.cs" />
+ <Compile Include="ViewExtensions.cs" />
+ <Compile Include="XamlLoader.cs" />
+ <Compile Include="XamlNode.cs" />
+ <Compile Include="XamlServiceProvider.cs" />
+ <Compile Include="XmlnsHelper.cs" />
+ <Compile Include="IExpressionParser.cs" />
+ <Compile Include="MarkupExtensionParser.cs" />
+ <Compile Include="TypeConversionExtensions.cs" />
+ <Compile Include="IDictionaryExtensions.cs" />
+ <Compile Include="MarkupExtensions\NullExtension.cs" />
+ <Compile Include="MarkupExtensions\ReferenceExtension.cs" />
+ <Compile Include="MarkupExtensions\StaticExtension.cs" />
+ <Compile Include="MarkupExtensions\TypeExtension.cs" />
+ <Compile Include="MarkupExtensions\ArrayExtension.cs" />
+ <Compile Include="XmlName.cs" />
+ <Compile Include="XamlNodeVisitor.cs" />
+ <Compile Include="NamescopingVisitor.cs" />
+ <Compile Include="CreateValuesVisitor.cs" />
+ <Compile Include="ApplyPropertiesVisitor.cs" />
+ <Compile Include="HydratationContext.cs" />
+ <Compile Include="RegisterXNamesVisitor.cs" />
+ <Compile Include="XamlParser.cs" />
+ <Compile Include="MarkupExtensions\BindingExtension.cs" />
+ <Compile Include="MarkupExtensions\StaticResourceExtension.cs" />
+ <Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
+ <Compile Include="ValueConverterProvider.cs" />
+ <Compile Include="FillResourceDictionariesVisitor.cs" />
+ <Compile Include="ExpandMarkupsVisitor.cs" />
+ <Compile Include="XamlCompilationAttribute.cs" />
+ <Compile Include="TypeArgumentsParser.cs" />
+ </ItemGroup>
+ <Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
+ <ItemGroup>
+ <ProjectReference Include="..\Xamarin.Forms.Core\Xamarin.Forms.Core.csproj">
+ <Project>{57B8B73D-C3B5-4C42-869E-7B2F17D354AC}</Project>
+ <Name>Xamarin.Forms.Core</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <ItemGroup />
+</Project> \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XamlCompilationAttribute.cs b/Xamarin.Forms.Xaml/XamlCompilationAttribute.cs
new file mode 100644
index 00000000..76199039
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XamlCompilationAttribute.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Reflection;
+
+namespace Xamarin.Forms.Xaml
+{
+ [Flags]
+ public enum XamlCompilationOptions
+ {
+ Skip = 1 << 0,
+ Compile = 1 << 1
+ }
+
+ [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Module | AttributeTargets.Class, Inherited = false)]
+ public sealed class XamlCompilationAttribute : Attribute
+ {
+ public XamlCompilationAttribute(XamlCompilationOptions xamlCompilationOptions)
+ {
+ XamlCompilationOptions = xamlCompilationOptions;
+ }
+
+ public XamlCompilationOptions XamlCompilationOptions { get; set; }
+ }
+
+ internal static class XamlCExtensions
+ {
+ public static bool IsCompiled(this Type type)
+ {
+ var attr = type.GetTypeInfo().GetCustomAttribute<XamlCompilationAttribute>();
+ if (attr != null)
+ return attr.XamlCompilationOptions == XamlCompilationOptions.Compile;
+ attr = type.GetTypeInfo().Module.GetCustomAttribute<XamlCompilationAttribute>();
+ if (attr != null)
+ return attr.XamlCompilationOptions == XamlCompilationOptions.Compile;
+ attr = type.GetTypeInfo().Assembly.GetCustomAttribute<XamlCompilationAttribute>();
+ if (attr != null)
+ return attr.XamlCompilationOptions == XamlCompilationOptions.Compile;
+
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XamlLoader.cs b/Xamarin.Forms.Xaml/XamlLoader.cs
new file mode 100644
index 00000000..6d7674b9
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XamlLoader.cs
@@ -0,0 +1,208 @@
+//
+// XamlLoader.cs
+//
+// Author:
+// Stephane Delcroix <stephane@mi8.be>
+//
+// Copyright (c) 2013 Mobile Inception
+// Copyright (c) 2013-2014 Xamarin, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal static class XamlLoader
+ {
+ static readonly Dictionary<Type, string> XamlResources = new Dictionary<Type, string>();
+
+ public static void Load(BindableObject view, Type callingType)
+ {
+ var xaml = GetXamlForType(callingType);
+ if (string.IsNullOrEmpty(xaml))
+ throw new XamlParseException(string.Format("No embeddedresources found for {0}", callingType), new XmlLineInfo());
+ Load(view, xaml);
+ }
+
+ public static void Load(BindableObject view, string xaml)
+ {
+ using(var reader = XmlReader.Create(new StringReader(xaml)))
+ {
+ while (reader.Read())
+ {
+ //Skip until element
+ if (reader.NodeType == XmlNodeType.Whitespace)
+ continue;
+ if (reader.NodeType != XmlNodeType.Element)
+ {
+ Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
+ continue;
+ }
+
+ var rootnode = new RuntimeRootNode(new XmlType(reader.NamespaceURI, reader.Name, null), view);
+
+ XamlParser.ParseXaml(rootnode, reader);
+
+ var visitorContext = new HydratationContext { RootElement = view };
+
+ rootnode.Accept(new XamlNodeVisitor((node, parent) => node.Parent = parent), null);
+ //set parents for {StaticResource}
+ rootnode.Accept(new ExpandMarkupsVisitor(visitorContext), null);
+ rootnode.Accept(new NamescopingVisitor(visitorContext), null); //set namescopes for {x:Reference}
+ rootnode.Accept(new CreateValuesVisitor(visitorContext), null);
+ rootnode.Accept(new RegisterXNamesVisitor(visitorContext), null);
+ rootnode.Accept(new FillResourceDictionariesVisitor(visitorContext), null);
+ rootnode.Accept(new ApplyPropertiesVisitor(visitorContext, true), null);
+ break;
+ }
+ }
+ }
+
+ static string GetXamlForType(Type type)
+ {
+ var assembly = type.GetTypeInfo().Assembly;
+
+ string resourceId;
+ if (XamlResources.TryGetValue(type, out resourceId))
+ {
+ var result = ReadResourceAsXaml(type, assembly, resourceId);
+ if (result != null)
+ return result;
+ }
+
+ var likelyResourceName = type.Name + ".xaml";
+ var resourceNames = assembly.GetManifestResourceNames();
+ string resourceName = null;
+
+ // first pass, pray to find it because the user named it correctly
+
+ string xaml = null;
+ foreach (var resource in resourceNames)
+ {
+ if (ResourceMatchesFilename(assembly, resource, likelyResourceName))
+ {
+ resourceName = resource;
+ xaml = ReadResourceAsXaml(type, assembly, resource);
+ if (xaml != null)
+ goto end;
+ }
+ }
+
+ // okay maybe they at least named it .xaml
+
+ foreach (var resource in resourceNames)
+ {
+ if (!resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
+ continue;
+
+ resourceName = resource;
+ xaml = ReadResourceAsXaml(type, assembly, resource);
+ if (xaml != null)
+ goto end;
+ }
+
+ foreach (var resource in resourceNames)
+ {
+ if (resource.EndsWith(".xaml", StringComparison.OrdinalIgnoreCase))
+ continue;
+
+ resourceName = resource;
+ xaml = ReadResourceAsXaml(type, assembly, resource, true);
+ if (xaml != null)
+ goto end;
+ }
+
+ end:
+ if (xaml == null)
+ return null;
+
+ XamlResources[type] = resourceName;
+ return xaml;
+ }
+
+ static bool ResourceMatchesFilename(Assembly assembly, string resource, string filename)
+ {
+ try
+ {
+ var info = assembly.GetManifestResourceInfo(resource);
+
+ if (!string.IsNullOrEmpty(info.FileName) &&
+ string.Compare(info.FileName, filename, StringComparison.OrdinalIgnoreCase) == 0)
+ return true;
+ }
+ catch (PlatformNotSupportedException)
+ {
+ // Because Win10 + .NET Native
+ }
+
+ if (resource.EndsWith("." + filename, StringComparison.OrdinalIgnoreCase) ||
+ string.Compare(resource, filename, StringComparison.OrdinalIgnoreCase) == 0)
+ return true;
+
+ return false;
+ }
+
+ static string ReadResourceAsXaml(Type type, Assembly assembly, string likelyTargetName, bool validate = false)
+ {
+ using(var stream = assembly.GetManifestResourceStream(likelyTargetName))
+ using(var reader = new StreamReader(stream))
+ {
+ if (validate)
+ {
+ // terrible validation of XML. Unfortunately it will probably work most of the time since comments
+ // also start with a <. We can't bring in any real deps.
+
+ var firstNonWhitespace = (char)reader.Read();
+ while (char.IsWhiteSpace(firstNonWhitespace))
+ firstNonWhitespace = (char)reader.Read();
+
+ if (firstNonWhitespace != '<')
+ return null;
+
+ stream.Seek(0, SeekOrigin.Begin);
+ }
+
+ var xaml = reader.ReadToEnd();
+
+ var pattern = String.Format("x:Class *= *\"{0}\"", type.FullName);
+ var regex = new Regex(pattern, RegexOptions.ECMAScript);
+ if (regex.IsMatch(xaml) || xaml.Contains(String.Format("x:Class=\"{0}\"", type.FullName)))
+ return xaml;
+ }
+ return null;
+ }
+
+ public class RuntimeRootNode : RootNode
+ {
+ public RuntimeRootNode(XmlType xmlType, object root) : base(xmlType)
+ {
+ Root = root;
+ }
+
+ public object Root { get; private set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XamlNode.cs b/Xamarin.Forms.Xaml/XamlNode.cs
new file mode 100644
index 00000000..2ef87c13
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XamlNode.cs
@@ -0,0 +1,234 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Xml;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal interface INode
+ {
+ List<string> IgnorablePrefixes { get; set; }
+
+ IXmlNamespaceResolver NamespaceResolver { get; }
+
+ INode Parent { get; set; }
+
+ void Accept(IXamlNodeVisitor visitor, INode parentNode);
+ }
+
+ internal interface IValueNode : INode
+ {
+ }
+
+ internal interface IElementNode : INode, IListNode
+ {
+ Dictionary<XmlName, INode> Properties { get; }
+
+ List<XmlName> SkipProperties { get; }
+
+ INameScope Namescope { get; }
+
+ XmlType XmlType { get; }
+
+ string NamespaceURI { get; }
+ }
+
+ internal interface IListNode : INode
+ {
+ List<INode> CollectionItems { get; }
+ }
+
+ [DebuggerDisplay("{NamespaceUri}:{Name}")]
+ internal class XmlType
+ {
+ public XmlType(string namespaceUri, string name, IList<XmlType> typeArguments)
+ {
+ NamespaceUri = namespaceUri;
+ Name = name;
+ TypeArguments = typeArguments;
+ }
+
+ public string NamespaceUri { get; }
+
+ public string Name { get; }
+
+ public IList<XmlType> TypeArguments { get; private set; }
+ }
+
+ internal abstract class BaseNode : IXmlLineInfo, INode
+ {
+ protected BaseNode(IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1)
+ {
+ NamespaceResolver = namespaceResolver;
+ LineNumber = linenumber;
+ LinePosition = lineposition;
+ }
+
+ public IXmlNamespaceResolver NamespaceResolver { get; }
+
+ public abstract void Accept(IXamlNodeVisitor visitor, INode parentNode);
+
+ public INode Parent { get; set; }
+
+ public List<string> IgnorablePrefixes { get; set; }
+
+ public bool HasLineInfo()
+ {
+ return LineNumber >= 0 && LinePosition >= 0;
+ }
+
+ public int LineNumber { get; set; }
+
+ public int LinePosition { get; set; }
+ }
+
+ [DebuggerDisplay("{Value}")]
+ internal class ValueNode : BaseNode, IValueNode
+ {
+ public ValueNode(object value, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1)
+ : base(namespaceResolver, linenumber, lineposition)
+ {
+ Value = value;
+ }
+
+ public object Value { get; set; }
+
+ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
+ {
+ visitor.Visit(this, parentNode);
+ }
+ }
+
+ [DebuggerDisplay("{MarkupString}")]
+ internal class MarkupNode : BaseNode, IValueNode
+ {
+ public MarkupNode(string markupString, IXmlNamespaceResolver namespaceResolver, int linenumber = -1,
+ int lineposition = -1)
+ : base(namespaceResolver, linenumber, lineposition)
+ {
+ MarkupString = markupString;
+ }
+
+ public string MarkupString { get; }
+
+ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
+ {
+ visitor.Visit(this, parentNode);
+ }
+ }
+
+ internal class ElementNode : BaseNode, IValueNode, IElementNode
+ {
+ public ElementNode(XmlType type, string namespaceURI, IXmlNamespaceResolver namespaceResolver, int linenumber = -1,
+ int lineposition = -1)
+ : base(namespaceResolver, linenumber, lineposition)
+ {
+ Properties = new Dictionary<XmlName, INode>();
+ SkipProperties = new List<XmlName>();
+ CollectionItems = new List<INode>();
+ XmlType = type;
+ NamespaceURI = namespaceURI;
+ }
+
+ public Dictionary<XmlName, INode> Properties { get; }
+
+ public List<XmlName> SkipProperties { get; }
+
+ public List<INode> CollectionItems { get; }
+
+ public XmlType XmlType { get; }
+
+ public string NamespaceURI { get; }
+
+ public INameScope Namescope { get; set; }
+
+ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
+ {
+ if (!visitor.VisitChildrenFirst)
+ visitor.Visit(this, parentNode);
+ if ((!visitor.StopOnDataTemplate || !IsDataTemplate(this, parentNode)) &&
+ (!visitor.StopOnResourceDictionary || !IsResourceDictionary(this, parentNode)))
+ {
+ foreach (var node in Properties.Values.ToList())
+ node.Accept(visitor, this);
+ foreach (var node in CollectionItems)
+ node.Accept(visitor, this);
+ }
+ if (visitor.VisitChildrenFirst)
+ visitor.Visit(this, parentNode);
+ }
+
+ static bool IsDataTemplate(INode node, INode parentNode)
+ {
+ var parentElement = parentNode as IElementNode;
+ INode createContent;
+ if (parentElement != null && parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) &&
+ createContent == node)
+ return true;
+ return false;
+ }
+
+ static bool IsResourceDictionary(INode node, INode parentNode)
+ {
+ var enode = node as ElementNode;
+ return enode.XmlType.Name == "ResourceDictionary";
+ }
+ }
+
+ internal abstract class RootNode : ElementNode
+ {
+ protected RootNode(XmlType xmlType) : base(xmlType, xmlType.NamespaceUri, null)
+ {
+ }
+
+ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
+ {
+ if (!visitor.VisitChildrenFirst)
+ visitor.Visit(this, parentNode);
+ foreach (var node in Properties.Values.ToList())
+ node.Accept(visitor, this);
+ foreach (var node in CollectionItems)
+ node.Accept(visitor, this);
+ if (visitor.VisitChildrenFirst)
+ visitor.Visit(this, parentNode);
+ }
+ }
+
+ internal class ListNode : BaseNode, IListNode, IValueNode
+ {
+ public ListNode(IList<INode> nodes, IXmlNamespaceResolver namespaceResolver, int linenumber = -1,
+ int lineposition = -1) : base(namespaceResolver, linenumber, lineposition)
+ {
+ CollectionItems = nodes.ToList();
+ }
+
+ public XmlName XmlName { get; set; }
+
+ public List<INode> CollectionItems { get; set; }
+
+ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
+ {
+ if (!visitor.VisitChildrenFirst)
+ visitor.Visit(this, parentNode);
+ foreach (var node in CollectionItems)
+ node.Accept(visitor, this);
+ if (visitor.VisitChildrenFirst)
+ visitor.Visit(this, parentNode);
+ }
+ }
+
+ internal static class INodeExtensions
+ {
+ public static bool SkipPrefix(this INode node, string prefix)
+ {
+ do
+ {
+ if (node.IgnorablePrefixes != null && node.IgnorablePrefixes.Contains(prefix))
+ return true;
+ node = node.Parent;
+ } while (node != null);
+ return false;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XamlNodeVisitor.cs b/Xamarin.Forms.Xaml/XamlNodeVisitor.cs
new file mode 100644
index 00000000..e0b1db32
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XamlNodeVisitor.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal interface IXamlNodeVisitor
+ {
+ bool VisitChildrenFirst { get; }
+
+ bool StopOnDataTemplate { get; }
+
+ bool StopOnResourceDictionary { get; }
+
+ void Visit(ValueNode node, INode parentNode);
+ void Visit(MarkupNode node, INode parentNode);
+ void Visit(ElementNode node, INode parentNode);
+ void Visit(RootNode node, INode parentNode);
+ void Visit(ListNode node, INode parentNode);
+ }
+
+ internal class XamlNodeVisitor : IXamlNodeVisitor
+ {
+ readonly Action<INode, INode> action;
+
+ public XamlNodeVisitor(Action<INode, INode> action, bool visitChildrenFirst = false, bool stopOnDataTemplate = false)
+ {
+ this.action = action;
+ VisitChildrenFirst = visitChildrenFirst;
+ StopOnDataTemplate = stopOnDataTemplate;
+ }
+
+ public bool VisitChildrenFirst { get; }
+
+ public bool StopOnDataTemplate { get; }
+
+ public bool StopOnResourceDictionary { get; private set; }
+
+ public void Visit(ValueNode node, INode parentNode)
+ {
+ action(node, parentNode);
+ }
+
+ public void Visit(MarkupNode node, INode parentNode)
+ {
+ action(node, parentNode);
+ }
+
+ public void Visit(ElementNode node, INode parentNode)
+ {
+ action(node, parentNode);
+ }
+
+ public void Visit(RootNode node, INode parentNode)
+ {
+ action(node, parentNode);
+ }
+
+ public void Visit(ListNode node, INode parentNode)
+ {
+ action(node, parentNode);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XamlParser.cs b/Xamarin.Forms.Xaml/XamlParser.cs
new file mode 100644
index 00000000..730c1624
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XamlParser.cs
@@ -0,0 +1,349 @@
+//
+// XamlParser.cs
+//
+// Author:
+// Stephane Delcroix <stephane@mi8.be>
+//
+// Copyright (c) 2013 Mobile Inception
+// Copyright (c) 2013-2014 Xamarin, Inc
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal static class XamlParser
+ {
+ public static void ParseXaml(RootNode rootNode, XmlReader reader)
+ {
+ var attributes = ParseXamlAttributes(reader);
+ rootNode.Properties.AddRange(attributes);
+ ParseXamlElementFor(rootNode, reader);
+ }
+
+ static void ParseXamlElementFor(IElementNode node, XmlReader reader)
+ {
+ Debug.Assert(reader.NodeType == XmlNodeType.Element);
+
+ var elementName = reader.Name;
+ var isEmpty = reader.IsEmptyElement;
+
+ if (isEmpty)
+ return;
+
+ while (reader.Read())
+ {
+ switch (reader.NodeType)
+ {
+ case XmlNodeType.EndElement:
+ Debug.Assert(reader.Name == elementName); //make sure we close the right element
+ return;
+ case XmlNodeType.Element:
+ // 1. Property Element.
+ if (reader.Name.Contains("."))
+ {
+ XmlName name;
+ if (reader.Name.StartsWith(elementName + ".", StringComparison.Ordinal))
+ name = new XmlName(reader.NamespaceURI, reader.Name.Substring(elementName.Length + 1));
+ else //Attached DP
+ name = new XmlName(reader.NamespaceURI, reader.LocalName);
+
+ var prop = ReadNode(reader);
+ if (prop != null)
+ node.Properties.Add(name, prop);
+ }
+ // 2. Xaml2009 primitives, x:Arguments, ...
+ else if (reader.NamespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml" && reader.LocalName == "Arguments")
+ {
+ var prop = ReadNode(reader);
+ if (prop != null)
+ node.Properties.Add(XmlName.xArguments, prop);
+ // 3. DataTemplate (should be handled by 4.)
+ }
+ else if (node.XmlType.NamespaceUri == "http://xamarin.com/schemas/2014/forms" &&
+ (node.XmlType.Name == "DataTemplate" || node.XmlType.Name == "ControlTemplate"))
+ {
+ var prop = ReadNode(reader, true);
+ if (prop != null)
+ node.Properties.Add(XmlName._CreateContent, prop);
+ // 4. Implicit content, implicit collection, or collection syntax. Add to CollectionItems, resolve case later.
+ }
+ else
+ {
+ var item = ReadNode(reader, true);
+ if (item != null)
+ node.CollectionItems.Add(item);
+ }
+ break;
+ case XmlNodeType.Whitespace:
+ break;
+ case XmlNodeType.Text:
+ node.CollectionItems.Add(new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader));
+ break;
+ default:
+ Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
+ break;
+ }
+ }
+ }
+
+ static INode ReadNode(XmlReader reader, bool nested = false)
+ {
+ var skipFirstRead = nested;
+ Debug.Assert(reader.NodeType == XmlNodeType.Element);
+ var name = reader.Name;
+ List<INode> nodes = new List<INode>();
+ INode node = null;
+
+ while (skipFirstRead || reader.Read())
+ {
+ skipFirstRead = false;
+
+ switch (reader.NodeType)
+ {
+ case XmlNodeType.EndElement:
+ Debug.Assert(reader.Name == name);
+ if (nodes.Count == 0) //Empty element
+ return null;
+ if (nodes.Count == 1)
+ return nodes[0];
+ return new ListNode(nodes, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
+ ((IXmlLineInfo)reader).LinePosition);
+ case XmlNodeType.Element:
+ var isEmpty = reader.IsEmptyElement && reader.Name == name;
+ var elementName = reader.Name;
+ var elementNsUri = reader.NamespaceURI;
+ var elementXmlInfo = (IXmlLineInfo)reader;
+
+ var attributes = ParseXamlAttributes(reader);
+
+ IList<XmlType> typeArguments = null;
+ if (attributes.Any(kvp => kvp.Key == XmlName.xTypeArguments))
+ {
+ typeArguments =
+ ((ValueNode)attributes.First(kvp => kvp.Key == XmlName.xTypeArguments).Value).Value as IList<XmlType>;
+ }
+
+ node = new ElementNode(new XmlType(elementNsUri, elementName, typeArguments), elementNsUri,
+ reader as IXmlNamespaceResolver, elementXmlInfo.LineNumber, elementXmlInfo.LinePosition);
+ ((IElementNode)node).Properties.AddRange(attributes);
+
+ ParseXamlElementFor((IElementNode)node, reader);
+ nodes.Add(node);
+ if (isEmpty || nested)
+ return node;
+ break;
+ case XmlNodeType.Text:
+ node = new ValueNode(reader.Value.Trim(), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
+ ((IXmlLineInfo)reader).LinePosition);
+ nodes.Add(node);
+ break;
+ case XmlNodeType.Whitespace:
+ break;
+ default:
+ Debug.WriteLine("Unhandled node {0} {1} {2}", reader.NodeType, reader.Name, reader.Value);
+ break;
+ }
+ }
+ throw new XamlParseException("Closing PropertyElement expected", (IXmlLineInfo)reader);
+ }
+
+ static IList<KeyValuePair<XmlName, INode>> ParseXamlAttributes(XmlReader reader)
+ {
+ Debug.Assert(reader.NodeType == XmlNodeType.Element);
+ var attributes = new List<KeyValuePair<XmlName, INode>>();
+ for (var i = 0; i < reader.AttributeCount; i++)
+ {
+ reader.MoveToAttribute(i);
+
+ //skip xmlns
+ if (reader.NamespaceURI == "http://www.w3.org/2000/xmlns/")
+ continue;
+
+ var propertyName = new XmlName(reader.NamespaceURI, reader.LocalName);
+
+ object value = reader.Value;
+
+ if (reader.NamespaceURI == "http://schemas.microsoft.com/winfx/2006/xaml")
+ {
+ switch (reader.Name)
+ {
+ case "x:Key":
+ propertyName = XmlName.xKey;
+ break;
+ case "x:Name":
+ propertyName = XmlName.xName;
+ break;
+ case "x:Class":
+ continue;
+ default:
+ Debug.WriteLine("Unhandled {0}", reader.Name);
+ continue;
+ }
+ }
+
+ if (reader.NamespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml")
+ {
+ switch (reader.Name)
+ {
+ case "x:Key":
+ propertyName = XmlName.xKey;
+ break;
+ case "x:Name":
+ propertyName = XmlName.xName;
+ break;
+ case "x:TypeArguments":
+ propertyName = XmlName.xTypeArguments;
+ value = TypeArgumentsParser.ParseExpression((string)value, (IXmlNamespaceResolver)reader, (IXmlLineInfo)reader);
+ break;
+ case "x:Class":
+ continue;
+ case "x:FactoryMethod":
+ propertyName = XmlName.xFactoryMethod;
+ break;
+ default:
+ Debug.WriteLine("Unhandled {0}", reader.Name);
+ continue;
+ }
+ }
+
+ var propertyNode = GetValueNode(value, reader);
+ attributes.Add(new KeyValuePair<XmlName, INode>(propertyName, propertyNode));
+ }
+ reader.MoveToElement();
+ return attributes;
+ }
+
+ static IValueNode GetValueNode(object value, XmlReader reader)
+ {
+ var valueString = value as string;
+ if (valueString != null && valueString.Trim().StartsWith("{}", StringComparison.Ordinal))
+ {
+ return new ValueNode(valueString.Substring(2), (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
+ ((IXmlLineInfo)reader).LinePosition);
+ }
+ if (valueString != null && valueString.Trim().StartsWith("{", StringComparison.Ordinal))
+ {
+ return new MarkupNode(valueString.Trim(), reader as IXmlNamespaceResolver, ((IXmlLineInfo)reader).LineNumber,
+ ((IXmlLineInfo)reader).LinePosition);
+ }
+ return new ValueNode(value, (IXmlNamespaceResolver)reader, ((IXmlLineInfo)reader).LineNumber,
+ ((IXmlLineInfo)reader).LinePosition);
+ }
+
+ public static Type GetElementType(XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly,
+ out XamlParseException exception)
+ {
+ var namespaceURI = xmlType.NamespaceUri;
+ var elementName = xmlType.Name;
+ var typeArguments = xmlType.TypeArguments;
+ exception = null;
+
+ List<Tuple<string, Assembly>> lookupAssemblies = new List<Tuple<string, Assembly>>();
+ List<string> lookupNames = new List<string>();
+
+ if (!XmlnsHelper.IsCustom(namespaceURI))
+ {
+ lookupAssemblies.Add(new Tuple<string, Assembly>("Xamarin.Forms", typeof (View).GetTypeInfo().Assembly));
+ lookupAssemblies.Add(new Tuple<string, Assembly>("Xamarin.Forms.Xaml", typeof (XamlLoader).GetTypeInfo().Assembly));
+ }
+ else if (namespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml" ||
+ namespaceURI == "http://schemas.microsoft.com/winfx/2006/xaml")
+ {
+ lookupAssemblies.Add(new Tuple<string, Assembly>("Xamarin.Forms.Xaml", typeof (XamlLoader).GetTypeInfo().Assembly));
+ lookupAssemblies.Add(new Tuple<string, Assembly>("System", typeof (object).GetTypeInfo().Assembly));
+ lookupAssemblies.Add(new Tuple<string, Assembly>("System", typeof (Uri).GetTypeInfo().Assembly)); //System.dll
+ }
+ else
+ {
+ string ns;
+ string typename;
+ string asmstring;
+ Assembly asm;
+
+ XmlnsHelper.ParseXmlns(namespaceURI, out typename, out ns, out asmstring);
+ asm = asmstring == null ? currentAssembly : Assembly.Load(new AssemblyName(asmstring));
+ lookupAssemblies.Add(new Tuple<string, Assembly>(ns, asm));
+ }
+
+ lookupNames.Add(elementName);
+ if (namespaceURI == "http://schemas.microsoft.com/winfx/2009/xaml")
+ lookupNames.Add(elementName + "Extension");
+ for (var i = 0; i < lookupNames.Count; i++)
+ {
+ var name = lookupNames[i];
+ if (name.Contains(":"))
+ name = name.Substring(name.LastIndexOf(':') + 1);
+ if (typeArguments != null)
+ name += "`" + typeArguments.Count; //this will return an open generic Type
+ lookupNames[i] = name;
+ }
+
+ Type type = null;
+ foreach (var asm in lookupAssemblies)
+ {
+ if (type != null)
+ break;
+ foreach (var name in lookupNames)
+ {
+ if (type != null)
+ break;
+ type = asm.Item2.GetType(asm.Item1 + "." + name);
+ }
+ }
+
+ if (type != null && typeArguments != null)
+ {
+ XamlParseException innerexception = null;
+ var args = typeArguments.Select(delegate(XmlType xmltype)
+ {
+ XamlParseException xpe;
+ var t = GetElementType(xmltype, xmlInfo, currentAssembly, out xpe);
+ if (xpe != null)
+ {
+ innerexception = xpe;
+ return null;
+ }
+ return t;
+ }).ToArray();
+ if (innerexception != null)
+ {
+ exception = innerexception;
+ return null;
+ }
+ type = type.MakeGenericType(args);
+ }
+
+ if (type == null)
+ {
+ exception = new XamlParseException(string.Format("Type {0} not found in xmlns {1}", elementName, namespaceURI),
+ xmlInfo);
+ return null;
+ }
+
+ return type;
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XamlServiceProvider.cs b/Xamarin.Forms.Xaml/XamlServiceProvider.cs
new file mode 100644
index 00000000..8998e6f5
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XamlServiceProvider.cs
@@ -0,0 +1,309 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Xml;
+using Xamarin.Forms.Internals;
+
+namespace Xamarin.Forms.Xaml.Internals
+{
+ public class XamlServiceProvider : IServiceProvider
+ {
+ readonly Dictionary<Type, object> services = new Dictionary<Type, object>();
+
+ internal XamlServiceProvider(INode node, HydratationContext context)
+ {
+ object targetObject;
+ if (node != null && node.Parent != null && context.Values.TryGetValue(node.Parent, out targetObject))
+ IProvideValueTarget = new XamlValueTargetProvider(targetObject, node, context, null);
+ if (context != null)
+ IRootObjectProvider = new XamlRootObjectProvider(context.RootElement);
+ if (node != null)
+ {
+ IXamlTypeResolver = new XamlTypeResolver(node.NamespaceResolver, XamlParser.GetElementType,
+ context.RootElement.GetType().GetTypeInfo().Assembly);
+
+ var enode = node;
+ while (enode != null && !(enode is IElementNode))
+ enode = enode.Parent;
+ if (enode != null)
+ INameScopeProvider = new NameScopeProvider { NameScope = (enode as IElementNode).Namescope };
+ }
+
+ var xmlLineInfo = node as IXmlLineInfo;
+ if (xmlLineInfo != null)
+ IXmlLineInfoProvider = new XmlLineInfoProvider(xmlLineInfo);
+
+ IValueConverterProvider = new ValueConverterProvider();
+ }
+
+ public XamlServiceProvider()
+ {
+ IValueConverterProvider = new ValueConverterProvider();
+ }
+
+ internal IProvideValueTarget IProvideValueTarget
+ {
+ get { return (IProvideValueTarget)GetService(typeof (IProvideValueTarget)); }
+ set { services[typeof (IProvideValueTarget)] = value; }
+ }
+
+ internal IXamlTypeResolver IXamlTypeResolver
+ {
+ get { return (IXamlTypeResolver)GetService(typeof (IXamlTypeResolver)); }
+ set { services[typeof (IXamlTypeResolver)] = value; }
+ }
+
+ internal IRootObjectProvider IRootObjectProvider
+ {
+ get { return (IRootObjectProvider)GetService(typeof (IRootObjectProvider)); }
+ set { services[typeof (IRootObjectProvider)] = value; }
+ }
+
+ internal IXmlLineInfoProvider IXmlLineInfoProvider
+ {
+ get { return (IXmlLineInfoProvider)GetService(typeof (IXmlLineInfoProvider)); }
+ set { services[typeof (IXmlLineInfoProvider)] = value; }
+ }
+
+ internal INameScopeProvider INameScopeProvider
+ {
+ get { return (INameScopeProvider)GetService(typeof (INameScopeProvider)); }
+ set { services[typeof (INameScopeProvider)] = value; }
+ }
+
+ internal IValueConverterProvider IValueConverterProvider
+ {
+ get { return (IValueConverterProvider)GetService(typeof (IValueConverterProvider)); }
+ set { services[typeof (IValueConverterProvider)] = value; }
+ }
+
+ public object GetService(Type serviceType)
+ {
+ object service;
+ return services.TryGetValue(serviceType, out service) ? service : null;
+ }
+
+ public void Add(Type type, object service)
+ {
+ services.Add(type, service);
+ }
+ }
+
+ internal class XamlValueTargetProvider : IProvideParentValues, IProvideValueTarget
+ {
+ public XamlValueTargetProvider(object targetObject, INode node, HydratationContext context, object targetProperty)
+ {
+ Context = context;
+ Node = node;
+ TargetObject = targetObject;
+ TargetProperty = targetProperty;
+ }
+
+ INode Node { get; }
+
+ HydratationContext Context { get; }
+
+ public object TargetObject { get; }
+
+ public object TargetProperty
+ {
+ get { throw new NotImplementedException(); }
+ private set { }
+ }
+
+ IEnumerable<object> IProvideParentValues.ParentObjects
+ {
+ get
+ {
+ if (Node == null || Context == null)
+ yield break;
+ var n = Node;
+ object obj = null;
+ var context = Context;
+ while (n.Parent != null && context != null)
+ {
+ if (n.Parent is IElementNode)
+ {
+ if (context.Values.TryGetValue(n.Parent, out obj))
+ yield return obj;
+ else
+ {
+ context = context.ParentContext;
+ continue;
+ }
+ }
+ n = n.Parent;
+ }
+ }
+ }
+ }
+
+ public class SimpleValueTargetProvider : IProvideParentValues, IProvideValueTarget
+ {
+ readonly object[] objectAndParents;
+
+ public SimpleValueTargetProvider(object[] objectAndParents)
+ {
+ if (objectAndParents == null)
+ throw new ArgumentNullException("objectAndParents");
+ if (objectAndParents.Length == 0)
+ throw new ArgumentException();
+
+ this.objectAndParents = objectAndParents;
+ }
+
+ IEnumerable<object> IProvideParentValues.ParentObjects
+ {
+ get { return objectAndParents; }
+ }
+
+ object IProvideValueTarget.TargetObject
+ {
+ get { return objectAndParents[0]; }
+ }
+
+ object IProvideValueTarget.TargetProperty
+ {
+ get { throw new NotImplementedException(); }
+ }
+ }
+
+ public class XamlTypeResolver : IXamlTypeResolver
+ {
+ readonly Assembly currentAssembly;
+ readonly GetTypeFromXmlName getTypeFromXmlName;
+ readonly IXmlNamespaceResolver namespaceResolver;
+
+ public XamlTypeResolver(IXmlNamespaceResolver namespaceResolver, Assembly currentAssembly)
+ : this(namespaceResolver, XamlParser.GetElementType, currentAssembly)
+ {
+ }
+
+ internal XamlTypeResolver(IXmlNamespaceResolver namespaceResolver, GetTypeFromXmlName getTypeFromXmlName,
+ Assembly currentAssembly)
+ {
+ this.currentAssembly = currentAssembly;
+ if (namespaceResolver == null)
+ throw new ArgumentNullException();
+ if (getTypeFromXmlName == null)
+ throw new ArgumentNullException();
+
+ this.namespaceResolver = namespaceResolver;
+ this.getTypeFromXmlName = getTypeFromXmlName;
+ }
+
+ Type IXamlTypeResolver.Resolve(string qualifiedTypeName, IServiceProvider serviceProvider)
+ {
+ XamlParseException e;
+ var type = Resolve(qualifiedTypeName, serviceProvider, out e);
+ if (e != null)
+ throw e;
+ return type;
+ }
+
+ bool IXamlTypeResolver.TryResolve(string qualifiedTypeName, out Type type)
+ {
+ XamlParseException exception;
+ type = Resolve(qualifiedTypeName, null, out exception);
+ return exception == null;
+ }
+
+ Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider, out XamlParseException exception)
+ {
+ exception = null;
+ var split = qualifiedTypeName.Split(':');
+ if (split.Length > 2)
+ return null;
+
+ string prefix, name;
+ if (split.Length == 2)
+ {
+ prefix = split[0];
+ name = split[1];
+ }
+ else
+ {
+ prefix = "";
+ name = split[0];
+ }
+
+ IXmlLineInfo xmlLineInfo = null;
+ if (serviceProvider != null)
+ {
+ var lineInfoProvider = serviceProvider.GetService(typeof (IXmlLineInfoProvider)) as IXmlLineInfoProvider;
+ if (lineInfoProvider != null)
+ xmlLineInfo = lineInfoProvider.XmlLineInfo;
+ }
+
+ var namespaceuri = string.IsNullOrEmpty(prefix) ? "" : namespaceResolver.LookupNamespace(prefix);
+ if (namespaceuri == null)
+ {
+ exception = new XamlParseException(string.Format("No xmlns declaration for prefix \"{0}\"", prefix), xmlLineInfo);
+ return null;
+ }
+
+ return getTypeFromXmlName(new XmlType(namespaceuri, name, null), xmlLineInfo, currentAssembly, out exception);
+ }
+
+ internal delegate Type GetTypeFromXmlName(
+ XmlType xmlType, IXmlLineInfo xmlInfo, Assembly currentAssembly, out XamlParseException exception);
+ }
+
+ internal class XamlRootObjectProvider : IRootObjectProvider
+ {
+ public XamlRootObjectProvider(object rootObject)
+ {
+ RootObject = rootObject;
+ }
+
+ public object RootObject { get; }
+ }
+
+ public class XmlLineInfoProvider : IXmlLineInfoProvider
+ {
+ public XmlLineInfoProvider(IXmlLineInfo xmlLineInfo)
+ {
+ XmlLineInfo = xmlLineInfo;
+ }
+
+ public IXmlLineInfo XmlLineInfo { get; }
+ }
+
+ internal interface INameScopeProvider
+ {
+ INameScope NameScope { get; }
+ }
+
+ public class NameScopeProvider : INameScopeProvider
+ {
+ public INameScope NameScope { get; set; }
+ }
+
+ public class XmlNamespaceResolver : IXmlNamespaceResolver
+ {
+ readonly Dictionary<string, string> namespaces = new Dictionary<string, string>();
+
+ public IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope)
+ {
+ throw new NotImplementedException();
+ }
+
+ public string LookupNamespace(string prefix)
+ {
+ string result;
+ if (namespaces.TryGetValue(prefix, out result))
+ return result;
+ return null;
+ }
+
+ public string LookupPrefix(string namespaceName)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void Add(string prefix, string ns)
+ {
+ namespaces.Add(prefix, ns);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XmlName.cs b/Xamarin.Forms.Xaml/XmlName.cs
new file mode 100644
index 00000000..e22a162d
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XmlName.cs
@@ -0,0 +1,58 @@
+using System.Diagnostics;
+
+namespace Xamarin.Forms.Xaml
+{
+ [DebuggerDisplay("{NamespaceURI}:{LocalName}")]
+ internal struct XmlName
+ {
+ public static readonly XmlName _CreateContent = new XmlName("_", "CreateContent");
+ public static readonly XmlName xKey = new XmlName("x", "Key");
+ public static readonly XmlName xName = new XmlName("x", "Name");
+ public static readonly XmlName xTypeArguments = new XmlName("x", "TypeArguments");
+ public static readonly XmlName xArguments = new XmlName("x", "Arguments");
+ public static readonly XmlName xFactoryMethod = new XmlName("x", "xFactoryMethod");
+
+ public string NamespaceURI { get; }
+
+ public string LocalName { get; }
+
+ public XmlName(string namespaceUri, string localName)
+ {
+ NamespaceURI = namespaceUri;
+ LocalName = localName;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj == null)
+ return false;
+ if (obj.GetType() != typeof (XmlName))
+ return false;
+ var other = (XmlName)obj;
+ return NamespaceURI == other.NamespaceURI && LocalName == other.LocalName;
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = 0;
+ if (NamespaceURI != null)
+ hashCode = NamespaceURI.GetHashCode();
+ if (LocalName != null)
+ hashCode = (hashCode * 397) ^ LocalName.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public static bool operator ==(XmlName x1, XmlName x2)
+ {
+ return x1.NamespaceURI == x2.NamespaceURI && x1.LocalName == x2.LocalName;
+ }
+
+ public static bool operator !=(XmlName x1, XmlName x2)
+ {
+ return !(x1 == x2);
+ }
+ }
+} \ No newline at end of file
diff --git a/Xamarin.Forms.Xaml/XmlnsHelper.cs b/Xamarin.Forms.Xaml/XmlnsHelper.cs
new file mode 100644
index 00000000..778d2947
--- /dev/null
+++ b/Xamarin.Forms.Xaml/XmlnsHelper.cs
@@ -0,0 +1,56 @@
+using System;
+
+namespace Xamarin.Forms.Xaml
+{
+ internal static class XmlnsHelper
+ {
+ public static bool IsCustom(string ns)
+ {
+ switch (ns)
+ {
+ case "":
+ case "http://xamarin.com/schemas/2014/forms":
+ return false;
+ }
+ return true;
+ }
+
+ public static string ParseNamespaceFromXmlns(string xmlns)
+ {
+ string typeName;
+ string ns;
+ string asm;
+
+ ParseXmlns(xmlns, out typeName, out ns, out asm);
+
+ return ns;
+ }
+
+ public static void ParseXmlns(string xmlns, out string typeName, out string ns, out string asm)
+ {
+ typeName = ns = asm = null;
+
+ foreach (var decl in xmlns.Split(';'))
+ {
+ if (decl.StartsWith("clr-namespace:", StringComparison.Ordinal))
+ {
+ ns = decl.Substring(14, decl.Length - 14);
+ continue;
+ }
+ if (decl.StartsWith("assembly=", StringComparison.Ordinal))
+ {
+ asm = decl.Substring(9, decl.Length - 9);
+ continue;
+ }
+ var nsind = decl.LastIndexOf(".", StringComparison.Ordinal);
+ if (nsind > 0)
+ {
+ ns = decl.Substring(0, nsind);
+ typeName = decl.Substring(nsind + 1, decl.Length - nsind - 1);
+ }
+ else
+ typeName = decl;
+ }
+ }
+ }
+} \ No newline at end of file