File: XamlNode.cs
Web Access
Project: src\src\Controls\src\Xaml\Controls.Xaml.csproj (Microsoft.Maui.Controls.Xaml)
#nullable disable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml;
using Microsoft.Maui.Controls.Internals;
 
namespace Microsoft.Maui.Controls.Xaml
{
	interface INode
	{
		List<string> IgnorablePrefixes { get; set; }
		IXmlNamespaceResolver NamespaceResolver { get; }
		INode Parent { get; set; }
 
		void Accept(IXamlNodeVisitor visitor, INode parentNode);
		INode Clone();
	}
 
	interface IValueNode : INode
	{
	}
 
	interface IElementNode : INode, IListNode
	{
		Dictionary<XmlName, INode> Properties { get; }
		List<XmlName> SkipProperties { get; }
		NameScopeRef NameScopeRef { get; }
		XmlType XmlType { get; }
		string NamespaceURI { get; }
	}
 
	interface IListNode : INode
	{
		List<INode> CollectionItems { get; }
	}
 
	class NameScopeRef
	{
		public INameScope NameScope { get; set; }
	}
 
	[DebuggerDisplay("{NamespaceUri}:{Name}")]
	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; }
 
		public override bool Equals(object obj)
		{
			if (obj is not XmlType other)
			{
				return false;
			}
 
			return
				NamespaceUri == other.NamespaceUri &&
				Name == other.Name &&
				(TypeArguments == null && other.TypeArguments == null ||
				 TypeArguments != null && other.TypeArguments != null && TypeArguments.SequenceEqual(other.TypeArguments));
		}
 
		public override int GetHashCode()
		{
			unchecked
			{
#if NETSTANDARD2_0
				int hashCode = NamespaceUri.GetHashCode();
				hashCode = (hashCode * 397) ^ Name.GetHashCode();
#else
				int hashCode = NamespaceUri.GetHashCode(StringComparison.Ordinal);
				hashCode = (hashCode * 397) ^ Name.GetHashCode(StringComparison.Ordinal);
#endif
				return hashCode;
			}
		}
	}
 
	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 INode Parent { get; set; }
		public List<string> IgnorablePrefixes { get; set; }
		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}")]
	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);
		}
 
		public override INode Clone() => new ValueNode(Value, NamespaceResolver, LineNumber, LinePosition)
		{
			IgnorablePrefixes = IgnorablePrefixes
		};
	}
 
	[DebuggerDisplay("{MarkupString}")]
	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);
		}
 
		public override INode Clone() => new MarkupNode(MarkupString, NamespaceResolver, LineNumber, LinePosition)
		{
			IgnorablePrefixes = IgnorablePrefixes
		};
	}
 
 
	[DebuggerDisplay("{XmlType.Name}")]
	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 NameScopeRef NameScopeRef { get; set; }
 
		public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
		{
			if (visitor.VisitingMode == TreeVisitingMode.TopDown && !SkipVisitNode(visitor, parentNode))
				visitor.Visit(this, parentNode);
 
			if (!SkipChildren(visitor, this, parentNode))
			{
				foreach (var node in Properties.Values.ToArray())
					node.Accept(visitor, this);
				foreach (var node in CollectionItems.ToArray())
					node.Accept(visitor, this);
			}
 
			if (visitor.VisitingMode == TreeVisitingMode.BottomUp && !SkipVisitNode(visitor, parentNode))
				visitor.Visit(this, parentNode);
 
		}
 
		bool IsDataTemplate(INode parentNode)
		{
			if (parentNode is IElementNode parentElement &&
				parentElement.Properties.TryGetValue(XmlName._CreateContent, out INode createContent) &&
				createContent == this)
				return true;
			return false;
		}
 
		protected bool SkipChildren(IXamlNodeVisitor visitor, INode node, INode parentNode) =>
			   (visitor.StopOnDataTemplate && IsDataTemplate(parentNode))
			|| (visitor.StopOnResourceDictionary && visitor.IsResourceDictionary(this))
			|| visitor.SkipChildren(node, parentNode);
 
		protected bool SkipVisitNode(IXamlNodeVisitor visitor, INode parentNode) =>
			!visitor.VisitNodeOnDataTemplate && IsDataTemplate(parentNode);
 
		public override INode Clone()
		{
			var clone = new ElementNode(XmlType, NamespaceURI, NamespaceResolver, LineNumber, LinePosition)
			{
				IgnorablePrefixes = IgnorablePrefixes
			};
			foreach (var kvp in Properties)
				clone.Properties.Add(kvp.Key, kvp.Value.Clone());
			foreach (var p in SkipProperties)
				clone.SkipProperties.Add(p);
			foreach (var p in CollectionItems)
				clone.CollectionItems.Add(p.Clone());
			return clone;
		}
	}
 
	abstract class RootNode : ElementNode
	{
		protected RootNode(XmlType xmlType, IXmlNamespaceResolver nsResolver, int linenumber = -1, int lineposition = -1) : base(xmlType, xmlType.NamespaceUri, nsResolver, linenumber: linenumber, lineposition: lineposition)
		{
		}
 
		public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
		{
			if (visitor.VisitingMode == TreeVisitingMode.TopDown && !SkipVisitNode(visitor, parentNode))
				visitor.Visit(this, parentNode);
 
			if (!SkipChildren(visitor, this, parentNode))
			{
				foreach (var node in Properties.Values.ToList())
					node.Accept(visitor, this);
				foreach (var node in CollectionItems.ToList())
					node.Accept(visitor, this);
			}
 
			if (visitor.VisitingMode == TreeVisitingMode.BottomUp && !SkipVisitNode(visitor, parentNode))
				visitor.Visit(this, parentNode);
		}
	}
 
	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.VisitingMode == TreeVisitingMode.TopDown)
				visitor.Visit(this, parentNode);
			foreach (var node in CollectionItems)
				node.Accept(visitor, this);
			if (visitor.VisitingMode == TreeVisitingMode.BottomUp)
				visitor.Visit(this, parentNode);
		}
 
		public override INode Clone()
		{
			var items = new List<INode>();
			foreach (var p in CollectionItems)
				items.Add(p.Clone());
			return new ListNode(items, NamespaceResolver, LineNumber, LinePosition)
			{
				IgnorablePrefixes = IgnorablePrefixes
			};
		}
	}
 
	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;
		}
	}
}