diff options
Diffstat (limited to 'Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs')
-rw-r--r-- | Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs | 207 |
1 files changed, 207 insertions, 0 deletions
diff --git a/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs new file mode 100644 index 00000000..e63ae426 --- /dev/null +++ b/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Xml; +using Xamarin.Forms.Xaml; +using Xamarin.Forms.Xaml.Internals; + +namespace Xamarin.Forms.Build.Tasks +{ + class ExpandMarkupsVisitor : IXamlNodeVisitor + { + readonly IList<XmlName> skips = new List<XmlName> + { + XmlName.xKey, + XmlName.xTypeArguments, + XmlName.xArguments, + XmlName.xFactoryMethod, + XmlName.xName + }; + + public ExpandMarkupsVisitor(ILContext context) + { + Context = context; + } + + ILContext 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) + { + XmlName propertyName; + if (!TryGetProperyName(markupnode, parentNode, out propertyName)) + return; + if (skips.Contains(propertyName)) + return; + var markupString = markupnode.MarkupString; + var node = ParseExpression(ref markupString, Context, markupnode.NamespaceResolver, markupnode) as IElementNode; + if (node != null) + { + ((IElementNode)parentNode).Properties[propertyName] = node; + node.Accept(new XamlNodeVisitor((n, parent) => n.Parent = parent), parentNode); + } + } + + public void Visit(ElementNode node, INode parentNode) + { + } + + public void Visit(RootNode node, INode parentNode) + { + } + + public void Visit(ListNode node, INode parentNode) + { + } + + public static bool TryGetProperyName(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 INode ParseExpression(ref string expression, ILContext context, IXmlNamespaceResolver nsResolver, + IXmlLineInfo xmlLineInfo) + { + if (expression.StartsWith("{}", StringComparison.Ordinal)) + return new ValueNode(expression.Substring(2), null); + + if (expression[expression.Length - 1] != '}') + throw new XamlParseException("Markup expression missing its closing tag", xmlLineInfo); + + int len; + string match; + if (!MarkupExpressionParser.MatchMarkup(out match, expression, out len)) + throw new XamlParseException("Error while parsing markup expression", xmlLineInfo); + expression = expression.Substring(len).TrimStart(); + if (expression.Length == 0) + throw new XamlParseException("Markup expression not closed", xmlLineInfo); + + var provider = new XamlServiceProvider(null, null); + provider.Add(typeof (ILContextProvider), new ILContextProvider(context)); + provider.Add(typeof (IXmlNamespaceResolver), nsResolver); + provider.Add(typeof (IXmlLineInfoProvider), new XmlLineInfoProvider(xmlLineInfo)); + + return new MarkupExpansionParser().Parse(match, ref expression, provider); + } + + class ILContextProvider + { + public ILContextProvider(ILContext context) + { + Context = context; + } + + public ILContext Context { get; } + } + + 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 contextProvider = serviceProvider.GetService(typeof (ILContextProvider)) as ILContextProvider; + + 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]; + } + + var namespaceuri = nsResolver.LookupNamespace(prefix) ?? ""; + //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. + XmlType type; + try + { + type = new XmlType(namespaceuri, name + "Extension", null); + var typeref = type.GetTypeReference(contextProvider.Context.Body.Method.Module, null); + } + catch (XamlParseException) + { + type = new XmlType(namespaceuri, name, null); + } + + if (type == null) + throw new NotSupportedException(); + + node = xmlLineInfo == null + ? new ElementNode(type, null, nsResolver) + : new ElementNode(type, 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) + { + if (prop != null) + { + var name = new XmlName(node.NamespaceURI, prop); + node.Properties[name] = value as INode ?? new ValueNode(strValue, null); + } + else //ContentProperty + node.CollectionItems.Add(value as INode ?? new ValueNode(strValue, null)); + } + } + } +}
\ No newline at end of file |