File: StyleSheets\Style.cs
Web Access
Project: src\src\Controls\src\Core\Controls.Core.csproj (Microsoft.Maui.Controls)
#nullable disable
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Xml;
using Microsoft.Maui.Controls.Xaml;
 
namespace Microsoft.Maui.Controls.StyleSheets
{
	sealed class Style
	{
		Style()
		{
		}
 
		public IDictionary<string, string> Declarations { get; set; } = new Dictionary<string, string>(StringComparer.Ordinal);
		Dictionary<KeyValuePair<string, string>, object> convertedValues = new Dictionary<KeyValuePair<string, string>, object>();
 
		public static Style Parse(CssReader reader, char stopChar = '\0')
		{
			Style style = new Style();
			string propertyName = null, propertyValue = null;
 
			int p;
			reader.SkipWhiteSpaces();
			bool readingName = true;
			while ((p = reader.Peek()) > 0)
			{
				switch (unchecked((char)p))
				{
					case ':':
						reader.Read();
						readingName = false;
						reader.SkipWhiteSpaces();
						break;
					case ';':
						reader.Read();
						if (!string.IsNullOrEmpty(propertyName) && !string.IsNullOrEmpty(propertyValue))
							style.Declarations.Add(propertyName, propertyValue);
						propertyName = propertyValue = null;
						readingName = true;
						reader.SkipWhiteSpaces();
						break;
					default:
						if ((char)p == stopChar)
							return style;
 
						if (readingName)
						{
							propertyName = reader.ReadIdent();
							if (propertyName == null)
								throw new Exception();
						}
						else
							propertyValue = reader.ReadUntil(stopChar, ';', ':');
						break;
				}
			}
			return style;
		}
 
		public void Apply(VisualElement styleable, bool inheriting = false)
		{
			if (styleable == null)
				throw new ArgumentNullException(nameof(styleable));
 
			foreach (var decl in Declarations)
			{
				var property = ((IStylable)styleable).GetProperty(decl.Key, inheriting);
				if (property == null)
					continue;
				if (string.Equals(decl.Value, "initial", StringComparison.OrdinalIgnoreCase))
					//FIXME
					styleable.ClearValue(property, new SetterSpecificity(SetterSpecificity.StyleImplicit, 0, 0, 0));
				else
				{
					object value;
					if (!convertedValues.TryGetValue(decl, out value))
						convertedValues[decl] = (value = Convert(styleable, decl.Value, property));
					//FIXME: compute distance
					styleable.SetValue(property, value, new SetterSpecificity(SetterSpecificity.StyleImplicit, 0, 0, 0));
				}
			}
 
			foreach (var child in ((IVisualTreeElement)styleable).GetVisualChildren())
			{
				var ve = child as VisualElement;
				if (ve == null)
					continue;
				Apply(ve, inheriting: true);
			}
		}
 
		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		static object Convert(object target, object value, BindableProperty property)
		{
			var serviceProvider = new StyleSheetServiceProvider(target, property);
			Func<MemberInfo> minforetriever =
				() =>
				{
					MemberInfo minfo = null;
					try
					{
						minfo = property.DeclaringType.GetRuntimeProperty(property.PropertyName);
					}
					catch (AmbiguousMatchException e)
					{
						throw new XamlParseException($"Multiple properties with name '{property.DeclaringType}.{property.PropertyName}' found.", serviceProvider, innerException: e);
					}
					if (minfo != null)
						return minfo;
					try
					{
						return property.DeclaringType.GetRuntimeMethod("Get" + property.PropertyName, new[] { typeof(BindableObject) });
					}
					catch (AmbiguousMatchException e)
					{
						throw new XamlParseException($"Multiple methods with name '{property.DeclaringType}.Get{property.PropertyName}' found.", serviceProvider, innerException: e);
					}
				};
			var ret = value.ConvertTo(property.ReturnType, minforetriever, serviceProvider, out Exception exception);
			if (exception != null)
				throw exception;
			return ret;
		}
 
		public void UnApply(IStylable styleable)
		{
			throw new NotImplementedException();
		}
	}
}