[Xaml[C]] Do not instantiate DataTemplate Content at parsing time (#683)
authorStephane Delcroix <stephane@delcroix.org>
Wed, 25 Jan 2017 13:47:27 +0000 (14:47 +0100)
committerKangho Hur <kangho.hur@samsung.com>
Fri, 24 Mar 2017 04:14:23 +0000 (13:14 +0900)
* [Xaml] rename VisitChildrenFirst

* [Xaml] rework SkipChildren in XamlNode

* [Xaml] fix 45179

* fix

20 files changed:
Xamarin.Forms.Build.Tasks/CreateObjectVisitor.cs
Xamarin.Forms.Build.Tasks/ExpandMarkupsVisitor.cs
Xamarin.Forms.Build.Tasks/SetFieldVisitor.cs
Xamarin.Forms.Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs
Xamarin.Forms.Build.Tasks/SetPropertiesVisitor.cs
Xamarin.Forms.Build.Tasks/SetResourcesVisitor.cs
Xamarin.Forms.Xaml.UnitTests/Issues/Bz27863.xaml.cs
Xamarin.Forms.Xaml.UnitTests/Issues/Bz27968.xaml.cs
Xamarin.Forms.Xaml.UnitTests/Issues/Bz45179.xaml [new file with mode: 0644]
Xamarin.Forms.Xaml.UnitTests/Issues/Bz45179.xaml.cs [new file with mode: 0644]
Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs
Xamarin.Forms.Xaml/CreateValuesVisitor.cs
Xamarin.Forms.Xaml/ExpandMarkupsVisitor.cs
Xamarin.Forms.Xaml/FillResourceDictionariesVisitor.cs
Xamarin.Forms.Xaml/MarkupExtensions/StaticResourceExtension.cs
Xamarin.Forms.Xaml/NamescopingVisitor.cs
Xamarin.Forms.Xaml/PruneIgnoredNodesVisitor.cs
Xamarin.Forms.Xaml/RegisterXNamesVisitor.cs
Xamarin.Forms.Xaml/XamlNode.cs
Xamarin.Forms.Xaml/XamlNodeVisitor.cs

index c684e51..b73926f 100644 (file)
@@ -21,20 +21,11 @@ namespace Xamarin.Forms.Build.Tasks
 
                ModuleDefinition Module { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return true; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
 
                public void Visit(ValueNode node, INode parentNode)
                {
index e647f6c..ff83de7 100644 (file)
@@ -24,20 +24,10 @@ namespace Xamarin.Forms.Build.Tasks
 
                ILContext Context { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
+               public bool StopOnDataTemplate => false;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => true;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index 1839cf7..9a5b744 100644 (file)
@@ -13,20 +13,10 @@ namespace Xamarin.Forms.Build.Tasks
 
                public ILContext Context { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index d810bd8..1c776ab 100644 (file)
@@ -16,20 +16,10 @@ namespace Xamarin.Forms.Build.Tasks
 
                ILContext Context { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index c93c14f..2c4ddbd 100644 (file)
@@ -37,8 +37,9 @@ namespace Xamarin.Forms.Build.Tasks
 
                public ILContext Context { get; }
                public bool StopOnResourceDictionary { get; }
-               public bool VisitChildrenFirst { get; } = true;
-               public bool StopOnDataTemplate { get; } = true;
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
+               public bool StopOnDataTemplate => true;
+               public bool VisitNodeOnDataTemplate => true;
 
                ModuleDefinition Module { get; }
 
@@ -88,6 +89,11 @@ namespace Xamarin.Forms.Build.Tasks
                        if ((propertyName != XmlName.Empty || TryGetPropertyName(node, parentNode, out propertyName)) && skips.Contains(propertyName))
                                return;
 
+                       if (propertyName == XmlName._CreateContent) {
+                               SetDataTemplate((IElementNode)parentNode, node, Context, node);
+                               return;
+                       }
+
                        //if this node is an IMarkupExtension, invoke ProvideValue() and replace the variable
                        var vardef = Context.Variables[node];
                        var vardefref = new VariableDefinitionReference(vardef);
@@ -114,11 +120,8 @@ namespace Xamarin.Forms.Build.Tasks
                                        return;
                                if (parentNode is IElementNode && ((IElementNode)parentNode).SkipProperties.Contains (propertyName))
                                        return;
-
-                               if (propertyName == XmlName._CreateContent)
-                                       SetDataTemplate((IElementNode)parentNode, node, Context, node);
-                               else
-                                       Context.IL.Append(SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node));
+                               
+                               Context.IL.Append(SetPropertyValue(Context.Variables[(IElementNode)parentNode], propertyName, node, Context, node));
                        }
                        else if (IsCollectionItem(node, parentNode) && parentNode is IElementNode)
                        {
index f046efc..c5be1fb 100644 (file)
@@ -1,6 +1,5 @@
 using System;
 using System.Collections;
-using System.Collections.Generic;
 using System.Linq;
 using Mono.Cecil;
 using Mono.Cecil.Cil;
@@ -20,26 +19,15 @@ namespace Xamarin.Forms.Build.Tasks
 
                ModuleDefinition Module { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
                public void Visit(ValueNode node, INode parentNode)
                {
                        XmlName propertyName;
-                       if (!SetPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName))
-                       {
+                       if (!SetPropertiesVisitor.TryGetPropertyName(node, parentNode, out propertyName)) {
                                if (!IsCollectionItem(node, parentNode))
                                        return;
                                string contentProperty;
index 03ebaaf..1120a03 100644 (file)
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using Xamarin.Forms;
 
 using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
 
 namespace Xamarin.Forms.Xaml.UnitTests
 {
@@ -22,6 +23,18 @@ namespace Xamarin.Forms.Xaml.UnitTests
                [TestFixture]
                class Tests
                {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
                        [TestCase(true)]
                        [TestCase(false)]
                        public void DataTemplateInResourceDictionaries (bool useCompiledXaml)
index 2482e9f..507073d 100644 (file)
@@ -4,6 +4,7 @@ using System.Collections.Generic;
 using Xamarin.Forms;
 
 using NUnit.Framework;
+using Xamarin.Forms.Core.UnitTests;
 
 namespace Xamarin.Forms.Xaml.UnitTests
 {
@@ -26,6 +27,18 @@ namespace Xamarin.Forms.Xaml.UnitTests
                [TestFixture]
                class Tests
                {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
                        [TestCase(true)]
                        [TestCase(false)]
                        public void BaseClassIdentifiersAreValidForResources (bool useCompiledXaml)
diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz45179.xaml b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz45179.xaml
new file mode 100644 (file)
index 0000000..a89aa29
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ContentPage 
+       xmlns="http://xamarin.com/schemas/2014/forms"
+       xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
+       xmlns:local="clr-namespace:Xamarin.Forms.Xaml.UnitTests"
+       x:Class="Xamarin.Forms.Xaml.UnitTests.Bz45179">
+       <ContentPage.Resources>
+               <ResourceDictionary>
+                       <DataTemplate x:Key="dt0">
+                               <local:Bz45179_0/>
+                       </DataTemplate>
+               </ResourceDictionary>
+       </ContentPage.Resources>
+</ContentPage>
\ No newline at end of file
diff --git a/Xamarin.Forms.Xaml.UnitTests/Issues/Bz45179.xaml.cs b/Xamarin.Forms.Xaml.UnitTests/Issues/Bz45179.xaml.cs
new file mode 100644 (file)
index 0000000..1dbdb44
--- /dev/null
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using Xamarin.Forms;
+using Xamarin.Forms.Core.UnitTests;
+
+namespace Xamarin.Forms.Xaml.UnitTests
+{
+       public class Bz45179_0 : ContentView {
+               public static int creator_count;
+               public Bz45179_0()
+               {
+                       creator_count++;
+               }
+
+       }
+       public partial class Bz45179 : ContentPage
+       {
+               public Bz45179()
+               {
+                       InitializeComponent();
+               }
+
+               public Bz45179(bool useCompiledXaml)
+               {
+                       //this stub will be replaced at compile time
+               }
+
+               [TestFixture]
+               class Tests
+               {
+                       [SetUp]
+                       public void Setup()
+                       {
+                               Device.PlatformServices = new MockPlatformServices();
+                       }
+
+                       [TearDown]
+                       public void TearDown()
+                       {
+                               Device.PlatformServices = null;
+                       }
+
+                       [TestCase(true)]
+                       [TestCase(false)]
+                       public void DTDoNotInstantiateTheirContent(bool useCompiledXaml)
+                       {
+                               Bz45179_0.creator_count = 0;
+                               Assume.That(Bz45179_0.creator_count, Is.EqualTo(0));
+                               var page = new Bz45179(useCompiledXaml);
+                               Assert.That(Bz45179_0.creator_count, Is.EqualTo(0));
+                       }
+               }
+       }
+}
\ No newline at end of file
index 154ba02..879ea3b 100644 (file)
@@ -35,15 +35,10 @@ namespace Xamarin.Forms.Xaml
 
                HydratationContext Context { get; }
 
-               public bool VisitChildrenFirst {
-                       get { return true; }
-               }
-
-               public bool StopOnDataTemplate {
-                       get { return true; }
-               }
-
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
+               public bool StopOnDataTemplate => true;
                public bool StopOnResourceDictionary { get; }
+               public bool VisitNodeOnDataTemplate => true;
 
                public void Visit(ValueNode node, INode parentNode)
                {
@@ -81,6 +76,15 @@ namespace Xamarin.Forms.Xaml
 
                public void Visit(ElementNode node, INode parentNode)
                {
+                       var propertyName = XmlName.Empty;
+                       if (TryGetPropertyName(node, parentNode, out propertyName) && propertyName == XmlName._CreateContent){
+                               var s0 = Values[parentNode];
+                               if (s0 is ElementTemplate) {
+                                       SetTemplate(s0 as ElementTemplate, node);
+                                       return;
+                               }
+                       }
+
                        var value = Values [node];
                        var parentElement = parentNode as IElementNode;
                        var markupExtension = value as IMarkupExtension;
@@ -96,7 +100,7 @@ namespace Xamarin.Forms.Xaml
                                value = valueProvider.ProvideValue(serviceProvider);
                        }
 
-                       XmlName propertyName = XmlName.Empty;
+                       propertyName = XmlName.Empty;
 
                        //Simplify ListNodes with single elements
                        var pList = parentNode as ListNode;
@@ -113,11 +117,7 @@ namespace Xamarin.Forms.Xaml
                                        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);
+                               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;
index 817b62c..b4f9213 100644 (file)
@@ -23,20 +23,10 @@ namespace Xamarin.Forms.Xaml
 
                HydratationContext Context { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index 8189350..282b7da 100644 (file)
@@ -28,20 +28,10 @@ namespace Xamarin.Forms.Xaml
 
                HydratationContext Context { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.BottomUp;
+               public bool StopOnDataTemplate => false;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => true;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index a3f1210..509d54e 100644 (file)
@@ -20,20 +20,10 @@ namespace Xamarin.Forms.Xaml
                        get { return Context.Values; }
                }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index 703fc20..34f1c56 100644 (file)
@@ -34,7 +34,7 @@ namespace Xamarin.Forms.Xaml
                        }
                        if (resource == null && (Application.Current == null || Application.Current.Resources == null ||
                                                                         !Application.Current.Resources.TryGetMergedValue(Key, out resource)))
-                               throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
+                                       throw new XamlParseException($"StaticResource not found for key {Key}", xmlLineInfo);
 
                        var bp = valueProvider.TargetProperty as BindableProperty;
                        var pi = valueProvider.TargetProperty as PropertyInfo;
index 0651d04..ec9b985 100644 (file)
@@ -14,20 +14,10 @@ namespace Xamarin.Forms.Xaml
 
                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 TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => false;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => true;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index 95ddb20..92badec 100644 (file)
@@ -5,17 +5,10 @@ namespace Xamarin.Forms.Xaml
 {
        class PruneIgnoredNodesVisitor : IXamlNodeVisitor
        {
-               public bool StopOnDataTemplate {
-                       get { return false; }
-               }
-
-               public bool StopOnResourceDictionary {
-                       get { return false; }
-               }
-
-               public bool VisitChildrenFirst {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => false;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => true;
 
                public void Visit(ElementNode node, INode parentNode)
                {
index a7da3b9..5ee3bc3 100644 (file)
@@ -12,20 +12,10 @@ namespace Xamarin.Forms.Xaml
 
                Dictionary<INode, object> Values { get; }
 
-               public bool VisitChildrenFirst
-               {
-                       get { return false; }
-               }
-
-               public bool StopOnDataTemplate
-               {
-                       get { return true; }
-               }
-
-               public bool StopOnResourceDictionary
-               {
-                       get { return false; }
-               }
+               public TreeVisitingMode VisitingMode => TreeVisitingMode.TopDown;
+               public bool StopOnDataTemplate => true;
+               public bool StopOnResourceDictionary => false;
+               public bool VisitNodeOnDataTemplate => false;
 
                public void Visit(ValueNode node, INode parentNode)
                {
index 752f384..eccf207 100644 (file)
@@ -1,4 +1,3 @@
-using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
@@ -7,36 +6,30 @@ using Xamarin.Forms.Internals;
 
 namespace Xamarin.Forms.Xaml
 {
-       internal interface INode
+       interface INode
        {
                List<string> IgnorablePrefixes { get; set; }
-
                IXmlNamespaceResolver NamespaceResolver { get; }
-
                INode Parent { get; set; }
 
                void Accept(IXamlNodeVisitor visitor, INode parentNode);
                INode Clone();
        }
 
-       internal interface IValueNode : INode
+       interface IValueNode : INode
        {
        }
 
-       internal interface IElementNode : INode, IListNode
+       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
+       interface IListNode : INode
        {
                List<INode> CollectionItems { get; }
        }
@@ -56,7 +49,7 @@ namespace Xamarin.Forms.Xaml
                public IList<XmlType> TypeArguments { get; }
        }
 
-       internal abstract class BaseNode : IXmlLineInfo, INode
+       abstract class BaseNode : IXmlLineInfo, INode
        {
                protected BaseNode(IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1)
                {
@@ -66,27 +59,19 @@ namespace Xamarin.Forms.Xaml
                }
 
                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; }
 
+               public bool HasLineInfo() => LineNumber >= 0 && LinePosition >= 0;
+
+               public abstract void Accept(IXamlNodeVisitor visitor, INode parentNode);
                public abstract INode Clone();
        }
 
        [DebuggerDisplay("{Value}")]
-       internal class ValueNode : BaseNode, IValueNode
+       class ValueNode : BaseNode, IValueNode
        {
                public ValueNode(object value, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1)
                        : base(namespaceResolver, linenumber, lineposition)
@@ -101,19 +86,15 @@ namespace Xamarin.Forms.Xaml
                        visitor.Visit(this, parentNode);
                }
 
-               public override INode Clone()
-               {
-                       return new ValueNode(Value, NamespaceResolver, LineNumber, LinePosition) {
-                               IgnorablePrefixes = IgnorablePrefixes
-                       };
-               }
+               public override INode Clone() => new ValueNode(Value, NamespaceResolver, LineNumber, LinePosition) {
+                       IgnorablePrefixes = IgnorablePrefixes
+               };
        }
 
        [DebuggerDisplay("{MarkupString}")]
-       internal class MarkupNode : BaseNode, IValueNode
+       class MarkupNode : BaseNode, IValueNode
        {
-               public MarkupNode(string markupString, IXmlNamespaceResolver namespaceResolver, int linenumber = -1,
-                       int lineposition = -1)
+               public MarkupNode(string markupString, IXmlNamespaceResolver namespaceResolver, int linenumber = -1, int lineposition = -1)
                        : base(namespaceResolver, linenumber, lineposition)
                {
                        MarkupString = markupString;
@@ -126,15 +107,13 @@ namespace Xamarin.Forms.Xaml
                        visitor.Visit(this, parentNode);
                }
 
-               public override INode Clone()
-               {
-                       return new MarkupNode(MarkupString, NamespaceResolver, LineNumber, LinePosition) {
-                               IgnorablePrefixes = IgnorablePrefixes
-                       };
-               }
+               public override INode Clone() => new MarkupNode(MarkupString, NamespaceResolver, LineNumber, LinePosition) {
+                       IgnorablePrefixes = IgnorablePrefixes
+               };
        }
 
-       internal class ElementNode : BaseNode, IValueNode, IElementNode
+       [DebuggerDisplay("{XmlType.Name}")]
+       class ElementNode : BaseNode, IValueNode, IElementNode
        {
                public ElementNode(XmlType type, string namespaceURI, IXmlNamespaceResolver namespaceResolver, int linenumber = -1,
                        int lineposition = -1)
@@ -148,48 +127,48 @@ namespace Xamarin.Forms.Xaml
                }
 
                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)
+                       if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.TopDown)
                                visitor.Visit(this, parentNode);
-                       if ((!visitor.StopOnDataTemplate || !IsDataTemplate(this, parentNode)) &&
-                           (!visitor.StopOnResourceDictionary || !IsResourceDictionary(this, parentNode)))
-                       {
+
+                       if (!SkipChildren(visitor, parentNode)) {
                                foreach (var node in Properties.Values.ToList())
                                        node.Accept(visitor, this);
                                foreach (var node in CollectionItems)
                                        node.Accept(visitor, this);
                        }
-                       if (visitor.VisitChildrenFirst)
+
+                       if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.BottomUp)
                                visitor.Visit(this, parentNode);
+
                }
 
-               internal static bool IsDataTemplate(INode node, INode parentNode)
+               bool IsDataTemplate(INode parentNode)
                {
                        var parentElement = parentNode as IElementNode;
                        INode createContent;
-                       if (parentElement != null && parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) &&
-                           createContent == node)
+                       if (parentElement != null &&
+                               parentElement.Properties.TryGetValue(XmlName._CreateContent, out createContent) &&
+                               createContent == this)
                                return true;
                        return false;
                }
 
-               internal static bool IsResourceDictionary(INode node, INode parentNode)
-               {
-                       var enode = node as ElementNode;
-                       return enode.XmlType.Name == "ResourceDictionary";
-               }
+               bool IsResourceDictionary() => XmlType.Name == "ResourceDictionary";
+
+               protected bool SkipChildren(IXamlNodeVisitor visitor, INode parentNode) =>
+                       (visitor.StopOnDataTemplate && IsDataTemplate(parentNode)) ||
+                       (visitor.StopOnResourceDictionary && IsResourceDictionary());
+
+               protected bool SkipVisitNode(IXamlNodeVisitor visitor, INode parentNode) =>
+                       !visitor.VisitNodeOnDataTemplate && IsDataTemplate(parentNode);
 
                public override INode Clone()
                {
@@ -206,7 +185,7 @@ namespace Xamarin.Forms.Xaml
                }
        }
 
-       internal abstract class RootNode : ElementNode
+       abstract class RootNode : ElementNode
        {
                protected RootNode(XmlType xmlType, IXmlNamespaceResolver nsResolver) : base(xmlType, xmlType.NamespaceUri, nsResolver)
                {
@@ -214,40 +193,39 @@ namespace Xamarin.Forms.Xaml
 
                public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
                {
-                       if (!visitor.VisitChildrenFirst)
+                       if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.TopDown)
                                visitor.Visit(this, parentNode);
-                       if ((!visitor.StopOnDataTemplate || !IsDataTemplate(this, parentNode)) &&
-                               (!visitor.StopOnResourceDictionary || !IsResourceDictionary(this, parentNode)))
-                       {
+
+                       if (!SkipChildren(visitor, parentNode)) {
                                foreach (var node in Properties.Values.ToList())
                                        node.Accept(visitor, this);
                                foreach (var node in CollectionItems)
                                        node.Accept(visitor, this);
                        }
-                       if (visitor.VisitChildrenFirst)
+
+                       if (!SkipVisitNode(visitor, parentNode) && visitor.VisitingMode == TreeVisitingMode.BottomUp)
                                visitor.Visit(this, parentNode);
                }
        }
 
-       internal class ListNode : BaseNode, IListNode, IValueNode
+       class ListNode : BaseNode, IListNode, IValueNode
        {
-               public ListNode(IList<INode> nodes, IXmlNamespaceResolver namespaceResolver, int linenumber = -1,
-                       int lineposition = -1) : base(namespaceResolver, linenumber, lineposition)
+               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)
+                       if (visitor.VisitingMode == TreeVisitingMode.TopDown)
                                visitor.Visit(this, parentNode);
                        foreach (var node in CollectionItems)
                                node.Accept(visitor, this);
-                       if (visitor.VisitChildrenFirst)
+                       if (visitor.VisitingMode == TreeVisitingMode.BottomUp)
                                visitor.Visit(this, parentNode);
                }
 
@@ -262,12 +240,11 @@ namespace Xamarin.Forms.Xaml
                }
        }
 
-       internal static class INodeExtensions
+       static class INodeExtensions
        {
                public static bool SkipPrefix(this INode node, string prefix)
                {
-                       do
-                       {
+                       do {
                                if (node.IgnorablePrefixes != null && node.IgnorablePrefixes.Contains(prefix))
                                        return true;
                                node = node.Parent;
index e0b1db3..874a5fb 100644 (file)
@@ -2,12 +2,11 @@
 
 namespace Xamarin.Forms.Xaml
 {
-       internal interface IXamlNodeVisitor
+       interface IXamlNodeVisitor
        {
-               bool VisitChildrenFirst { get; }
-
+               TreeVisitingMode VisitingMode { get; }
                bool StopOnDataTemplate { get; }
-
+               bool VisitNodeOnDataTemplate { get; }
                bool StopOnResourceDictionary { get; }
 
                void Visit(ValueNode node, INode parentNode);
@@ -17,22 +16,27 @@ namespace Xamarin.Forms.Xaml
                void Visit(ListNode node, INode parentNode);
        }
 
-       internal class XamlNodeVisitor : IXamlNodeVisitor
+       enum TreeVisitingMode {
+               TopDown,
+               BottomUp
+       }
+
+       class XamlNodeVisitor : IXamlNodeVisitor
        {
                readonly Action<INode, INode> action;
 
-               public XamlNodeVisitor(Action<INode, INode> action, bool visitChildrenFirst = false, bool stopOnDataTemplate = false)
+               public XamlNodeVisitor(Action<INode, INode> action, TreeVisitingMode visitingMode = TreeVisitingMode.TopDown, bool stopOnDataTemplate = false, bool visitNodeOnDataTemplate = true)
                {
                        this.action = action;
-                       VisitChildrenFirst = visitChildrenFirst;
+                       VisitingMode = visitingMode;
                        StopOnDataTemplate = stopOnDataTemplate;
+                       VisitNodeOnDataTemplate = visitNodeOnDataTemplate;
                }
 
-               public bool VisitChildrenFirst { get; }
-
+               public TreeVisitingMode VisitingMode { get; }
                public bool StopOnDataTemplate { get; }
-
                public bool StopOnResourceDictionary { get; private set; }
+               public bool VisitNodeOnDataTemplate { get; }
 
                public void Visit(ValueNode node, INode parentNode)
                {