summaryrefslogtreecommitdiff
path: root/Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs')
-rw-r--r--Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs207
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