Restructure properties API

This commit is contained in:
Daniel Cronqvist 2024-08-24 21:38:40 +02:00
parent 3a6684a52d
commit 1c1ba326b2
20 changed files with 282 additions and 62 deletions

View file

@ -91,7 +91,7 @@ public static partial class DotTiledAssert
AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID)); AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID));
AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite)); AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite));
AssertProperties(actual.Properties, expected.Properties); AssertPropertiesList(actual.Properties, expected.Properties);
Assert.NotNull(actual.Tilesets); Assert.NotNull(actual.Tilesets);
AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count"); AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count");

View file

@ -21,6 +21,25 @@ public static partial class DotTiledAssert
} }
} }
internal static void AssertPropertiesList(IList<IProperty>? expected, IList<IProperty>? actual)
{
if (expected is null)
{
Assert.Null(actual);
return;
}
Assert.NotNull(actual);
AssertEqual(expected.Count, actual.Count, "Properties.Count");
foreach (var prop in expected)
{
Assert.Contains(actual, p => p.Name == prop.Name);
var actualProp = actual.First(p => p.Name == prop.Name);
AssertProperty((dynamic)prop, (dynamic)actualProp);
}
}
private static void AssertProperty(IProperty expected, IProperty actual) private static void AssertProperty(IProperty expected, IProperty actual)
{ {
AssertEqual(expected.Type, actual.Type, "Property.Type"); AssertEqual(expected.Type, actual.Type, "Property.Type");
@ -45,6 +64,6 @@ public static partial class DotTiledAssert
private static void AssertProperty(ClassProperty expected, ClassProperty actual) private static void AssertProperty(ClassProperty expected, ClassProperty actual)
{ {
AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType"); AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType");
AssertProperties(expected.Properties, actual.Properties); AssertPropertiesList(expected.Value, actual.Value);
} }
} }

View file

@ -55,15 +55,15 @@ public partial class TestData
} }
} }
], ],
Properties = new Dictionary<string, IProperty> Properties =
{ [
["boolprop"] = new BoolProperty { Name = "boolprop", Value = true }, new BoolProperty { Name = "boolprop", Value = true },
["colorprop"] = new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) }, new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) },
["fileprop"] = new FileProperty { Name = "fileprop", Value = "file.txt" }, new FileProperty { Name = "fileprop", Value = "file.txt" },
["floatprop"] = new FloatProperty { Name = "floatprop", Value = 4.2f }, new FloatProperty { Name = "floatprop", Value = 4.2f },
["intprop"] = new IntProperty { Name = "intprop", Value = 8 }, new IntProperty { Name = "intprop", Value = 8 },
["objectprop"] = new ObjectProperty { Name = "objectprop", Value = 5 }, new ObjectProperty { Name = "objectprop", Value = 5 },
["stringprop"] = new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" } new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" }
} ]
}; };
} }

View file

@ -55,24 +55,22 @@ public partial class TestData
} }
} }
], ],
Properties = new Dictionary<string, IProperty> Properties = [
{ new ClassProperty
["customclassprop"] = new ClassProperty
{ {
Name = "customclassprop", Name = "customclassprop",
PropertyType = "CustomClass", PropertyType = "CustomClass",
Properties = new Dictionary<string, IProperty> Value = [
{ new BoolProperty { Name = "boolinclass", Value = true },
["boolinclass"] = new BoolProperty { Name = "boolinclass", Value = true }, new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) },
["colorinclass"] = new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) }, new FileProperty { Name = "fileinclass", Value = "" },
["fileinclass"] = new FileProperty { Name = "fileinclass", Value = "" }, new FloatProperty { Name = "floatinclass", Value = 13.37f },
["floatinclass"] = new FloatProperty { Name = "floatinclass", Value = 13.37f }, new IntProperty { Name = "intinclass", Value = 0 },
["intinclass"] = new IntProperty { Name = "intinclass", Value = 0 }, new ObjectProperty { Name = "objectinclass", Value = 0 },
["objectinclass"] = new ObjectProperty { Name = "objectinclass", Value = 0 }, new StringProperty { Name = "stringinclass", Value = "This is a set string" }
["stringinclass"] = new StringProperty { Name = "stringinclass", Value = "This is a set string" } ]
}
} }
} ]
}; };
// This comes from map-with-custom-type-props/propertytypes.json // This comes from map-with-custom-type-props/propertytypes.json

View file

@ -90,7 +90,7 @@ public enum StaggerIndex
/// <summary> /// <summary>
/// Represents a Tiled map. /// Represents a Tiled map.
/// </summary> /// </summary>
public class Map public class Map : HasPropertiesBase
{ {
/// <summary> /// <summary>
/// The TMX format version. Is incremented to match minor Tiled releases. /// The TMX format version. Is incremented to match minor Tiled releases.
@ -191,7 +191,10 @@ public class Map
/// <summary> /// <summary>
/// Map properties. /// Map properties.
/// </summary> /// </summary>
public Dictionary<string, IProperty>? Properties { get; set; } public List<IProperty> Properties { get; set; } = [];
/// <inheritdoc/>
public override IList<IProperty> GetProperties() => Properties;
/// <summary> /// <summary>
/// List of tilesets used by the map. /// List of tilesets used by the map.

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a boolean property. /// Represents a boolean property.
/// </summary> /// </summary>
public class BoolProperty : IProperty public class BoolProperty : IProperty<bool>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
namespace DotTiled.Model; namespace DotTiled.Model;
@ -6,7 +7,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a class property. /// Represents a class property.
/// </summary> /// </summary>
public class ClassProperty : IProperty public class ClassProperty : IHasProperties, IProperty<IList<IProperty>>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }
@ -23,13 +24,32 @@ public class ClassProperty : IProperty
/// <summary> /// <summary>
/// The properties of the class property. /// The properties of the class property.
/// </summary> /// </summary>
public required Dictionary<string, IProperty> Properties { get; set; } public required IList<IProperty> Value { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public IProperty Clone() => new ClassProperty public IProperty Clone() => new ClassProperty
{ {
Name = Name, Name = Name,
PropertyType = PropertyType, PropertyType = PropertyType,
Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone()) Value = Value.Select(property => property.Clone()).ToList()
}; };
/// <inheritdoc/>
public IList<IProperty> GetProperties() => Value;
/// <inheritdoc/>
public T GetProperty<T>(string name) where T : IProperty => throw new System.NotImplementedException();
/// <inheritdoc/>
public bool TryGetProperty<T>(string name, [NotNullWhen(true)] out T? property) where T : IProperty
{
if (Value.FirstOrDefault(_properties => _properties.Name == name) is T prop)
{
property = prop;
return true;
}
property = default;
return false;
}
} }

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a color property. /// Represents a color property.
/// </summary> /// </summary>
public class ColorProperty : IProperty public class ColorProperty : IProperty<Color>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a file property. /// Represents a file property.
/// </summary> /// </summary>
public class FileProperty : IProperty public class FileProperty : IProperty<string>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a float property. /// Represents a float property.
/// </summary> /// </summary>
public class FloatProperty : IProperty public class FloatProperty : IProperty<float>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace DotTiled.Model;
/// <summary>
/// Interface for objects that have properties attached to them.
/// </summary>
public interface IHasProperties
{
/// <summary>
/// The properties attached to the object.
/// </summary>
IList<IProperty> GetProperties();
/// <summary>
/// Tries to get a property of the specified type with the specified name.
/// </summary>
/// <typeparam name="T">The type of the property to get.</typeparam>
/// <param name="name">The name of the property to get.</param>
/// <param name="property">The property with the specified name, if found.</param>
/// <returns>True if a property with the specified name was found; otherwise, false.</returns>
bool TryGetProperty<T>(string name, out T? property) where T : IProperty;
/// <summary>
/// Gets a property of the specified type with the specified name.
/// </summary>
/// <typeparam name="T">The type of the property to get.</typeparam>
/// <param name="name">The name of the property to get.</param>
/// <returns>The property with the specified name.</returns>
T GetProperty<T>(string name) where T : IProperty;
}
/// <summary>
/// Base class for objects that have properties attached to them.
/// </summary>
public abstract class HasPropertiesBase : IHasProperties
{
/// <inheritdoc/>
public abstract IList<IProperty> GetProperties();
/// <inheritdoc/>
public T GetProperty<T>(string name) where T : IProperty => throw new System.NotImplementedException();
/// <inheritdoc/>
public bool TryGetProperty<T>(string name, [NotNullWhen(true)] out T? property) where T : IProperty
{
var properties = GetProperties();
if (properties.FirstOrDefault(_properties => _properties.Name == name) is T prop)
{
property = prop;
return true;
}
property = default;
return false;
}
}

View file

@ -22,3 +22,15 @@ public interface IProperty
/// <returns>An identical, but non-reference-equal, instance of the same property.</returns> /// <returns>An identical, but non-reference-equal, instance of the same property.</returns>
IProperty Clone(); IProperty Clone();
} }
/// <summary>
/// Interface for properties that can be attached to objects, tiles, tilesets, maps etc.
/// </summary>
/// <typeparam name="T">The type of the property value.</typeparam>
public interface IProperty<T> : IProperty
{
/// <summary>
/// The value of the property.
/// </summary>
public T Value { get; set; }
}

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents an integer property. /// Represents an integer property.
/// </summary> /// </summary>
public class IntProperty : IProperty public class IntProperty : IProperty<int>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents an object property. /// Represents an object property.
/// </summary> /// </summary>
public class ObjectProperty : IProperty public class ObjectProperty : IProperty<uint>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -3,7 +3,7 @@ namespace DotTiled.Model;
/// <summary> /// <summary>
/// Represents a string property. /// Represents a string property.
/// </summary> /// </summary>
public class StringProperty : IProperty public class StringProperty : IProperty<string>
{ {
/// <inheritdoc/> /// <inheritdoc/>
public required string Name { get; set; } public required string Name { get; set; }

View file

@ -73,6 +73,9 @@ internal static partial class Helpers
}; };
} }
internal static List<IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
customClassDefinition.Members.Select(x => x.Clone()).ToList();
internal static Dictionary<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty>? overrideProperties) internal static Dictionary<string, IProperty> MergeProperties(Dictionary<string, IProperty>? baseProperties, Dictionary<string, IProperty>? overrideProperties)
{ {
if (baseProperties is null) if (baseProperties is null)
@ -93,7 +96,7 @@ internal static partial class Helpers
{ {
if (value is ClassProperty classProp) if (value is ClassProperty classProp)
{ {
((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); ((ClassProperty)baseProp).Value = MergePropertiesList(((ClassProperty)baseProp).Value, classProp.Value);
} }
else else
{ {
@ -105,6 +108,48 @@ internal static partial class Helpers
return result; return result;
} }
internal static IList<IProperty> MergePropertiesList(IList<IProperty>? baseProperties, IList<IProperty>? overrideProperties)
{
if (baseProperties is null)
return overrideProperties ?? [];
if (overrideProperties is null)
return baseProperties;
var result = baseProperties.Select(x => x.Clone()).ToList();
foreach (var overrideProp in overrideProperties)
{
if (!result.Any(x => x.Name == overrideProp.Name))
{
result.Add(overrideProp);
continue;
}
else
{
var existingProp = result.First(x => x.Name == overrideProp.Name);
if (existingProp is ClassProperty classProp)
{
classProp.Value = MergePropertiesList(classProp.Value, ((ClassProperty)overrideProp).Value);
}
else
{
ReplacePropertyInList(result, overrideProp);
}
}
}
return result;
}
internal static void ReplacePropertyInList(List<IProperty> properties, IProperty property)
{
var index = properties.FindIndex(p => p.Name == property.Name);
if (index == -1)
properties.Add(property);
else
properties[index] = property;
}
internal static void SetAtMostOnce<T>(ref T? field, T value, string fieldName) internal static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
{ {
if (field is not null) if (field is not null)

View file

@ -58,7 +58,7 @@ internal partial class Tmj
var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid"); var nextObjectID = element.GetRequiredProperty<uint>("nextobjectid");
var infinite = element.GetOptionalProperty<bool>("infinite", false); var infinite = element.GetOptionalProperty<bool>("infinite", false);
var properties = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null); var properties = element.GetOptionalPropertyCustom<List<IProperty>?>("properties", el => ReadPropertiesList(el, customTypeDefinitions), null);
List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); List<BaseLayer> layers = element.GetOptionalPropertyCustom<List<BaseLayer>>("layers", e => e.GetValueAsList<BaseLayer>(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []);
List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []); List<Tileset> tilesets = element.GetOptionalPropertyCustom<List<Tileset>>("tilesets", e => e.GetValueAsList<Tileset>(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []);
@ -84,7 +84,7 @@ internal partial class Tmj
NextLayerID = nextLayerID, NextLayerID = nextLayerID,
NextObjectID = nextObjectID, NextObjectID = nextObjectID,
Infinite = infinite, Infinite = infinite,
Properties = properties, Properties = properties ?? [],
Tilesets = tilesets, Tilesets = tilesets,
Layers = layers Layers = layers
}; };

View file

@ -43,6 +43,41 @@ internal partial class Tmj
return property!; return property!;
}).ToDictionary(p => p.Name); }).ToDictionary(p => p.Name);
internal static List<IProperty> ReadPropertiesList(
JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<IProperty>(e =>
{
var name = e.GetRequiredProperty<string>("name");
var type = e.GetOptionalPropertyParseable<PropertyType>("type", s => s switch
{
"string" => PropertyType.String,
"int" => PropertyType.Int,
"float" => PropertyType.Float,
"bool" => PropertyType.Bool,
"color" => PropertyType.Color,
"file" => PropertyType.File,
"object" => PropertyType.Object,
"class" => PropertyType.Class,
_ => throw new JsonException("Invalid property type")
}, PropertyType.String);
IProperty property = type switch
{
PropertyType.String => new StringProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty<int>("value") },
PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty<float>("value") },
PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty<bool>("value") },
PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable<Color>("value") },
PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty<string>("value") },
PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty<uint>("value") },
PropertyType.Class => ReadClassProperty(e, customTypeDefinitions),
_ => throw new JsonException("Invalid property type")
};
return property!;
});
internal static ClassProperty ReadClassProperty( internal static ClassProperty ReadClassProperty(
JsonElement element, JsonElement element,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
@ -54,28 +89,28 @@ internal partial class Tmj
if (customTypeDef is CustomClassDefinition ccd) if (customTypeDef is CustomClassDefinition ccd)
{ {
var propsInType = CreateInstanceOfCustomClass(ccd); var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
var props = element.GetOptionalPropertyCustom<Dictionary<string, IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []);
var mergedProps = Helpers.MergeProperties(propsInType, props); var mergedProps = Helpers.MergePropertiesList(propsInType, props);
return new ClassProperty return new ClassProperty
{ {
Name = name, Name = name,
PropertyType = propertyType, PropertyType = propertyType,
Properties = mergedProps Value = props
}; };
} }
throw new JsonException($"Unknown custom class '{propertyType}'."); throw new JsonException($"Unknown custom class '{propertyType}'.");
} }
internal static Dictionary<string, IProperty> ReadCustomClassProperties( internal static List<IProperty> ReadCustomClassProperties(
JsonElement element, JsonElement element,
CustomClassDefinition customClassDefinition, CustomClassDefinition customClassDefinition,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{ {
Dictionary<string, IProperty> resultingProps = []; List<IProperty> resultingProps = Helpers.CreateInstanceOfCustomClass(customClassDefinition);
foreach (var prop in customClassDefinition.Members) foreach (var prop in customClassDefinition.Members)
{ {
@ -95,12 +130,9 @@ internal partial class Tmj
_ => throw new JsonException("Invalid property type") _ => throw new JsonException("Invalid property type")
}; };
resultingProps[prop.Name] = property; Helpers.ReplacePropertyInList(resultingProps, property);
} }
return resultingProps; return resultingProps;
} }
internal static Dictionary<string, IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone());
} }

View file

@ -61,7 +61,7 @@ internal partial class Tmx
var infinite = (reader.GetOptionalAttributeParseable<uint>("infinite") ?? 0) == 1; var infinite = (reader.GetOptionalAttributeParseable<uint>("infinite") ?? 0) == 1;
// At most one of // At most one of
Dictionary<string, IProperty>? properties = null; List<IProperty>? properties = null;
// Any number of // Any number of
List<BaseLayer> layers = []; List<BaseLayer> layers = [];
@ -69,7 +69,7 @@ internal partial class Tmx
reader.ProcessChildren("map", (r, elementName) => elementName switch reader.ProcessChildren("map", (r, elementName) => elementName switch
{ {
"properties" => () => Helpers.SetAtMostOnce(ref properties, ReadProperties(r, customTypeDefinitions), "Properties"), "properties" => () => Helpers.SetAtMostOnce(ref properties, ReadPropertiesList(r, customTypeDefinitions), "Properties"),
"tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), "tileset" => () => tilesets.Add(ReadTileset(r, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)),
"layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)), "layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)),
"objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)), "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)),
@ -99,7 +99,7 @@ internal partial class Tmx
NextLayerID = nextLayerID, NextLayerID = nextLayerID,
NextObjectID = nextObjectID, NextObjectID = nextObjectID,
Infinite = infinite, Infinite = infinite,
Properties = properties, Properties = properties ?? [],
Tilesets = tilesets, Tilesets = tilesets,
Layers = layers Layers = layers
}; };

View file

@ -43,6 +43,42 @@ internal partial class Tmx
}).ToDictionary(x => x.name, x => x.property); }).ToDictionary(x => x.name, x => x.property);
} }
internal static List<IProperty> ReadPropertiesList(
XmlReader reader,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
{
return reader.ReadList("properties", "property", (r) =>
{
var name = r.GetRequiredAttribute("name");
var type = r.GetOptionalAttributeEnum<PropertyType>("type", (s) => s switch
{
"string" => PropertyType.String,
"int" => PropertyType.Int,
"float" => PropertyType.Float,
"bool" => PropertyType.Bool,
"color" => PropertyType.Color,
"file" => PropertyType.File,
"object" => PropertyType.Object,
"class" => PropertyType.Class,
_ => throw new XmlException("Invalid property type")
}) ?? PropertyType.String;
IProperty property = type switch
{
PropertyType.String => new StringProperty { Name = name, Value = r.GetRequiredAttribute("value") },
PropertyType.Int => new IntProperty { Name = name, Value = r.GetRequiredAttributeParseable<int>("value") },
PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable<float>("value") },
PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable<bool>("value") },
PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable<Color>("value") },
PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") },
PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable<uint>("value") },
PropertyType.Class => ReadClassProperty(r, customTypeDefinitions),
_ => throw new XmlException("Invalid property type")
};
return property;
});
}
internal static ClassProperty ReadClassProperty( internal static ClassProperty ReadClassProperty(
XmlReader reader, XmlReader reader,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions)
@ -54,18 +90,14 @@ internal partial class Tmx
if (customTypeDef is CustomClassDefinition ccd) if (customTypeDef is CustomClassDefinition ccd)
{ {
reader.ReadStartElement("property"); reader.ReadStartElement("property");
var propsInType = CreateInstanceOfCustomClass(ccd); var propsInType = Helpers.CreateInstanceOfCustomClass(ccd);
var props = ReadProperties(reader, customTypeDefinitions); var props = ReadPropertiesList(reader, customTypeDefinitions);
var mergedProps = Helpers.MergePropertiesList(propsInType, props);
var mergedProps = Helpers.MergeProperties(propsInType, props);
reader.ReadEndElement(); reader.ReadEndElement();
return new ClassProperty { Name = name, PropertyType = propertyType, Properties = mergedProps }; return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps };
} }
throw new XmlException($"Unkonwn custom class definition: {propertyType}"); throw new XmlException($"Unkonwn custom class definition: {propertyType}");
} }
internal static Dictionary<string, IProperty> CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) =>
customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone());
} }