1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
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;
}
}
}
|