Files
MonoGame.Extended/Source/MonoGame.Extended.Gui/Markup/MarkupParser.cs
T
Dylan WilsonandGitHub e962cec448 A mix between Tiled and GUI stuff (#553)
* mostly restored the tiled importer unit tests

* code cleanup

* code cleanup

* almost got tiled maps serialization independent of the content pipeline

* refactored the tiled map content importer so that tiled maps can be loaded at runtime

* code cleanup

* removed nuspec files

* experimenting with an XML based GUI markup parser

* a few more markup parser features

* finally got a working open file dialog

* experimenting with markup bindings

* attached properties

* working on the gui stuff again

* working on a better textbox

* multiline textboxing

* new text box is really coming along

* removed the content explorer experiment

* restored the old gui demo back to the way it was before (for now)
2018-10-09 22:18:48 +10:00

148 lines
5.5 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Gui.Controls;
namespace MonoGame.Extended.Gui.Markup
{
public class MarkupParser
{
public MarkupParser()
{
}
private static readonly Dictionary<string, Type> _controlTypes =
typeof(Control).Assembly
.ExportedTypes
.Where(t => t.IsSubclassOf(typeof(Control)))
.ToDictionary(t => t.Name, StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<Type, Func<string, object>> _converters =
new Dictionary<Type, Func<string, object>>
{
{typeof(object), s => s},
{typeof(string), s => s},
{typeof(bool), s => bool.Parse(s)},
{typeof(int), s => int.Parse(s)},
{typeof(Color), s => s.StartsWith("#") ? ColorHelper.FromHex(s) : ColorHelper.FromName(s)}
};
private static object ConvertValue(Type propertyType, string input, object dataContext)
{
var value = ParseBinding(input, dataContext);
if (_converters.TryGetValue(propertyType, out var converter))
return converter(value); //property.SetValue(control, converter(value));
if (propertyType.IsEnum)
return
Enum.Parse(propertyType, value,
true); // property.SetValue(control, Enum.Parse(propertyType, value, true));
throw new InvalidOperationException($"Converter not found for {propertyType}");
}
private static object ParseChildNode(XmlNode node, Control parent, object dataContext)
{
if (node is XmlText)
return node.InnerText.Trim();
if (_controlTypes.TryGetValue(node.Name, out var type))
{
var typeInfo = type.GetTypeInfo();
var control = (Control) Activator.CreateInstance(type);
// ReSharper disable once AssignNullToNotNullAttribute
foreach (var attribute in node.Attributes.Cast<XmlAttribute>())
{
var property = typeInfo.GetProperty(attribute.Name);
if (property != null)
{
var value = ConvertValue(property.PropertyType, attribute.Value, dataContext);
property.SetValue(control, value);
}
else
{
var parts = attribute.Name.Split('.');
var parentType = parts[0];
var propertyName = parts[1];
var propertyType = parent.GetAttachedPropertyType(propertyName);
var propertyValue = ConvertValue(propertyType, attribute.Value, dataContext);
if (!string.Equals(parent.GetType().Name, parentType, StringComparison.OrdinalIgnoreCase))
throw new InvalidOperationException(
$"Attached properties are only supported on the immediate parent type {parentType}");
control.SetAttachedProperty(propertyName, propertyValue);
}
}
if (node.HasChildNodes)
{
switch (control)
{
case ContentControl contentControl:
if (node.ChildNodes.Count > 1)
throw new InvalidOperationException("A content control can only have one child");
contentControl.Content = ParseChildNode(node.ChildNodes[0], control, dataContext);
break;
case LayoutControl layoutControl:
foreach (var childControl in ParseChildNodes(node.ChildNodes, control, dataContext))
layoutControl.Items.Add(childControl as Control);
break;
}
}
return control;
}
throw new InvalidOperationException($"Unknown control type {node.Name}");
}
private static string ParseBinding(string expression, object dataContext)
{
if (dataContext != null && expression.StartsWith("{{") && expression.EndsWith("}}"))
{
var binding = expression.Substring(2, expression.Length - 4);
var bindingValue = dataContext
.GetType()
.GetProperty(binding)
?.GetValue(dataContext);
return $"{bindingValue}";
}
return expression;
}
private static IEnumerable<object> ParseChildNodes(XmlNodeList nodes, Control parent, object dataContext)
{
foreach (var node in nodes.Cast<XmlNode>())
{
if (node.Name == "xml")
{
// TODO: Validate header
}
else
{
yield return ParseChildNode(node, parent, dataContext);
}
}
}
public Control Parse(string filePath, object dataContext)
{
var d = new XmlDocument();
d.Load(filePath);
return ParseChildNodes(d.ChildNodes, null, dataContext)
.LastOrDefault() as Control;
}
}
}