File: StyleSheets\Selector.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
 
namespace Microsoft.Maui.Controls.StyleSheets
{
	abstract class Selector
	{
		Selector()
		{
		}
 
		public static Selector Parse(CssReader reader, char stopChar = '\0')
		{
			Selector root = All, workingRoot = All;
			Operator workingRootParent = null;
			Action<Operator, Selector> setCurrentSelector = (op, sel) => SetCurrentSelector(ref root, ref workingRoot, ref workingRootParent, op, sel);
 
			int p;
			reader.SkipWhiteSpaces();
			while ((p = reader.Peek()) > 0)
			{
				switch (unchecked((char)p))
				{
					case '*':
						setCurrentSelector(new And(), All);
						reader.Read();
						break;
					case '.':
						reader.Read();
						var className = reader.ReadIdent();
						if (className == null)
							return Invalid;
						setCurrentSelector(new And(), new Class(className));
						break;
					case '#':
						reader.Read();
						var id = reader.ReadName();
						if (id == null)
							return Invalid;
						setCurrentSelector(new And(), new Id(id));
						break;
					case '[':
						throw new NotImplementedException("Attributes not implemented");
					case ',':
						reader.Read();
						setCurrentSelector(new Or(), All);
						reader.SkipWhiteSpaces();
						break;
					case '+':
						reader.Read();
						setCurrentSelector(new Adjacent(), All);
						reader.SkipWhiteSpaces();
						break;
					case '~':
						reader.Read();
						setCurrentSelector(new Sibling(), All);
						reader.SkipWhiteSpaces();
						break;
					case '>':
						reader.Read();
						setCurrentSelector(new Child(), All);
						reader.SkipWhiteSpaces();
						break;
					case '^':               //not in CSS spec
						reader.Read();
						var element = reader.ReadIdent();
						if (element == null)
							return Invalid;
						setCurrentSelector(new And(), new Base(element));
						break;
					case ' ':
					case '\t':
					case '\n':
					case '\r':
					case '\f':
						reader.Read();
						bool processWs = false;
						while ((p = reader.Peek()) > 0)
						{
							var c = unchecked((char)p);
							if (char.IsWhiteSpace(c))
							{
								reader.Read();
								continue;
							}
							processWs = (c != '+'
										&& c != '>'
										&& c != ','
										&& c != '~'
										&& c != stopChar);
							break;
						}
						if (!processWs)
							break;
						setCurrentSelector(new Descendent(), All);
						reader.SkipWhiteSpaces();
						break;
					default:
						if (unchecked((char)p) == stopChar)
							return root;
 
						var elementName = reader.ReadIdent();
						if (elementName == null)
							return Invalid;
						setCurrentSelector(new And(), new Element(elementName));
						break;
				}
			}
			return root;
		}
 
		static void SetCurrentSelector(ref Selector root, ref Selector workingRoot, ref Operator workingRootParent, Operator op, Selector sel)
		{
			var updateRoot = root == workingRoot;
 
			op.Left = workingRoot;
			op.Right = sel;
			workingRoot = op;
			if (workingRootParent != null)
				workingRootParent.Right = workingRoot;
 
			if (updateRoot)
				root = workingRoot;
 
			if (workingRoot is Or)
			{
				workingRootParent = (Operator)workingRoot;
				workingRoot = sel;
			}
		}
 
		public abstract bool Matches(IStyleSelectable styleable);
 
		internal static Selector Invalid = new Generic(s => false);
		internal static Selector All = new Generic(s => true);
 
		abstract class UnarySelector : Selector
		{
		}
 
		abstract class Operator : Selector
		{
			public Selector Left { get; set; } = Invalid;
			public Selector Right { get; set; } = Invalid;
		}
 
		sealed class Generic : UnarySelector
		{
			readonly Func<IStyleSelectable, bool> func;
			public Generic(Func<IStyleSelectable, bool> func)
			{
				this.func = func;
			}
 
			public override bool Matches(IStyleSelectable styleable) => func(styleable);
		}
 
		sealed class Class : UnarySelector
		{
			public Class(string className)
			{
				ClassName = className;
			}
 
			public string ClassName { get; }
			public override bool Matches(IStyleSelectable styleable)
				=> styleable.Classes != null && styleable.Classes.Contains(ClassName);
		}
 
		sealed class Id : UnarySelector
		{
			public Id(string id)
			{
				IdName = id;
			}
 
			public string IdName { get; }
			public override bool Matches(IStyleSelectable styleable) => styleable.Id == IdName;
		}
 
		sealed class Or : Operator
		{
			public override bool Matches(IStyleSelectable styleable) => Right.Matches(styleable) || Left.Matches(styleable);
		}
 
		sealed class And : Operator
		{
			public override bool Matches(IStyleSelectable styleable) => Right.Matches(styleable) && Left.Matches(styleable);
		}
 
		sealed class Element : UnarySelector
		{
			public Element(string elementName)
			{
				ElementName = elementName;
			}
 
			public string ElementName { get; }
			public override bool Matches(IStyleSelectable styleable) =>
				string.Equals(styleable.NameAndBases[0], ElementName, StringComparison.OrdinalIgnoreCase);
		}
 
		sealed class Base : UnarySelector
		{
			public Base(string elementName)
			{
				ElementName = elementName;
			}
 
			public string ElementName { get; }
			public override bool Matches(IStyleSelectable styleable)
			{
				for (var i = 0; i < styleable.NameAndBases.Length; i++)
					if (string.Equals(styleable.NameAndBases[i], ElementName, StringComparison.OrdinalIgnoreCase))
						return true;
				return false;
			}
		}
 
		sealed class Child : Operator
		{
			public override bool Matches(IStyleSelectable styleable) =>
				Right.Matches(styleable) && styleable.Parent != null && Left.Matches(styleable.Parent);
		}
 
		sealed class Descendent : Operator
		{
			public override bool Matches(IStyleSelectable styleable)
			{
				if (!Right.Matches(styleable))
					return false;
				var parent = styleable.Parent;
				while (parent != null)
				{
					if (Left.Matches(parent))
						return true;
					parent = parent.Parent;
				}
				return false;
			}
		}
 
		sealed class Adjacent : Operator
		{
			public override bool Matches(IStyleSelectable styleable)
			{
				if (!Right.Matches(styleable))
					return false;
				if (styleable.Parent == null)
					return false;
 
				IStyleSelectable prev = null;
				foreach (var elem in styleable.Parent.Children)
				{
					if (elem == styleable && prev != null)
						return Left.Matches(prev);
					prev = elem;
				}
				return false;
				//var index = styleable.Parent.Children.IndexOf(styleable);
				//if (index == 0)
				//	return false;
				//var adjacent = styleable.Parent.Children[index - 1];
				//return Left.Matches(adjacent);
			}
		}
 
		sealed class Sibling : Operator
		{
			public override bool Matches(IStyleSelectable styleable)
			{
				if (!Right.Matches(styleable))
					return false;
				if (styleable.Parent == null)
					return false;
 
				int selfIndex = 0;
				bool foundSelfInParent = false;
				foreach (var elem in styleable.Parent.Children)
				{
					if (elem == styleable)
					{
						foundSelfInParent = true;
						break;
					}
					++selfIndex;
				}
 
				if (!foundSelfInParent)
					return false;
 
				int index = 0;
				foreach (var elem in styleable.Parent.Children)
				{
					if (index >= selfIndex)
						return false;
					if (Left.Matches(elem))
						return true;
					++index;
				}
 
				return false;
 
				//var index = styleable.Parent.Children.IndexOf(styleable);
				//if (index == 0)
				//	return false;
				//int siblingIndex = -1;
				//for (var i = 0; i < index; i++)
				//	if (Left.Matches(styleable.Parent.Children[i])) {
				//		siblingIndex = i;
				//		break;
				//	}
				//return siblingIndex != -1;
			}
		}
	}
}