diff options
author | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 13:02:25 -0700 |
---|---|---|
committer | Jason Smith <jason.smith@xamarin.com> | 2016-03-22 16:13:41 -0700 |
commit | 17fdde66d94155fc62a034fa6658995bef6fd6e5 (patch) | |
tree | b5e5073a2a7b15cdbe826faa5c763e270a505729 /Xamarin.Forms.Xaml | |
download | xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.gz xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.tar.bz2 xamarin-forms-17fdde66d94155fc62a034fa6658995bef6fd6e5.zip |
Initial import
Diffstat (limited to 'Xamarin.Forms.Xaml')
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 |