diff --git a/src/DotTiled.Tests/Assert/AssertMap.cs b/src/DotTiled.Tests/Assert/AssertMap.cs index 6984b79..6f25f45 100644 --- a/src/DotTiled.Tests/Assert/AssertMap.cs +++ b/src/DotTiled.Tests/Assert/AssertMap.cs @@ -91,7 +91,7 @@ public static partial class DotTiledAssert AssertEqual(expected.NextObjectID, actual.NextObjectID, nameof(Map.NextObjectID)); AssertEqual(expected.Infinite, actual.Infinite, nameof(Map.Infinite)); - AssertProperties(actual.Properties, expected.Properties); + AssertPropertiesList(actual.Properties, expected.Properties); Assert.NotNull(actual.Tilesets); AssertEqual(expected.Tilesets.Count, actual.Tilesets.Count, "Tilesets.Count"); diff --git a/src/DotTiled.Tests/Assert/AssertProperties.cs b/src/DotTiled.Tests/Assert/AssertProperties.cs index 646d6eb..ad4dc53 100644 --- a/src/DotTiled.Tests/Assert/AssertProperties.cs +++ b/src/DotTiled.Tests/Assert/AssertProperties.cs @@ -21,6 +21,25 @@ public static partial class DotTiledAssert } } + internal static void AssertPropertiesList(IList? expected, IList? 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) { AssertEqual(expected.Type, actual.Type, "Property.Type"); @@ -45,6 +64,6 @@ public static partial class DotTiledAssert private static void AssertProperty(ClassProperty expected, ClassProperty actual) { AssertEqual(expected.PropertyType, actual.PropertyType, "ClassProperty.PropertyType"); - AssertProperties(expected.Properties, actual.Properties); + AssertPropertiesList(expected.Value, actual.Value); } } diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs b/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs index ba5cf4f..d5d8c1e 100644 --- a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs +++ b/src/DotTiled.Tests/Serialization/TestData/Map/map-with-common-props/map-with-common-props.cs @@ -55,15 +55,15 @@ public partial class TestData } } ], - Properties = new Dictionary - { - ["boolprop"] = new BoolProperty { Name = "boolprop", Value = true }, - ["colorprop"] = new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) }, - ["fileprop"] = new FileProperty { Name = "fileprop", Value = "file.txt" }, - ["floatprop"] = new FloatProperty { Name = "floatprop", Value = 4.2f }, - ["intprop"] = new IntProperty { Name = "intprop", Value = 8 }, - ["objectprop"] = new ObjectProperty { Name = "objectprop", Value = 5 }, - ["stringprop"] = new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" } - } + Properties = + [ + new BoolProperty { Name = "boolprop", Value = true }, + new ColorProperty { Name = "colorprop", Value = Color.Parse("#ff55ffff", CultureInfo.InvariantCulture) }, + new FileProperty { Name = "fileprop", Value = "file.txt" }, + new FloatProperty { Name = "floatprop", Value = 4.2f }, + new IntProperty { Name = "intprop", Value = 8 }, + new ObjectProperty { Name = "objectprop", Value = 5 }, + new StringProperty { Name = "stringprop", Value = "This is a string, hello world!" } + ] }; } diff --git a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs b/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs index 758c5c3..2c79478 100644 --- a/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs +++ b/src/DotTiled.Tests/Serialization/TestData/Map/map-with-custom-type-props/map-with-custom-type-props.cs @@ -55,24 +55,22 @@ public partial class TestData } } ], - Properties = new Dictionary - { - ["customclassprop"] = new ClassProperty + Properties = [ + new ClassProperty { Name = "customclassprop", PropertyType = "CustomClass", - Properties = new Dictionary - { - ["boolinclass"] = new BoolProperty { Name = "boolinclass", Value = true }, - ["colorinclass"] = new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) }, - ["fileinclass"] = new FileProperty { Name = "fileinclass", Value = "" }, - ["floatinclass"] = new FloatProperty { Name = "floatinclass", Value = 13.37f }, - ["intinclass"] = new IntProperty { Name = "intinclass", Value = 0 }, - ["objectinclass"] = new ObjectProperty { Name = "objectinclass", Value = 0 }, - ["stringinclass"] = new StringProperty { Name = "stringinclass", Value = "This is a set string" } - } + Value = [ + new BoolProperty { Name = "boolinclass", Value = true }, + new ColorProperty { Name = "colorinclass", Value = Color.Parse("#000000ff", CultureInfo.InvariantCulture) }, + new FileProperty { Name = "fileinclass", Value = "" }, + new FloatProperty { Name = "floatinclass", Value = 13.37f }, + new IntProperty { Name = "intinclass", Value = 0 }, + new ObjectProperty { Name = "objectinclass", Value = 0 }, + new StringProperty { Name = "stringinclass", Value = "This is a set string" } + ] } - } + ] }; // This comes from map-with-custom-type-props/propertytypes.json diff --git a/src/DotTiled/Model/Map.cs b/src/DotTiled/Model/Map.cs index c9c72dc..48ed975 100644 --- a/src/DotTiled/Model/Map.cs +++ b/src/DotTiled/Model/Map.cs @@ -90,7 +90,7 @@ public enum StaggerIndex /// /// Represents a Tiled map. /// -public class Map +public class Map : HasPropertiesBase { /// /// The TMX format version. Is incremented to match minor Tiled releases. @@ -191,7 +191,10 @@ public class Map /// /// Map properties. /// - public Dictionary? Properties { get; set; } + public List Properties { get; set; } = []; + + /// + public override IList GetProperties() => Properties; /// /// List of tilesets used by the map. diff --git a/src/DotTiled/Model/Properties/BoolProperty.cs b/src/DotTiled/Model/Properties/BoolProperty.cs index bc60a87..315e820 100644 --- a/src/DotTiled/Model/Properties/BoolProperty.cs +++ b/src/DotTiled/Model/Properties/BoolProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents a boolean property. /// -public class BoolProperty : IProperty +public class BoolProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Model/Properties/ClassProperty.cs b/src/DotTiled/Model/Properties/ClassProperty.cs index 50d65ba..eb7efce 100644 --- a/src/DotTiled/Model/Properties/ClassProperty.cs +++ b/src/DotTiled/Model/Properties/ClassProperty.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; namespace DotTiled.Model; @@ -6,7 +7,7 @@ namespace DotTiled.Model; /// /// Represents a class property. /// -public class ClassProperty : IProperty +public class ClassProperty : IHasProperties, IProperty> { /// public required string Name { get; set; } @@ -23,13 +24,32 @@ public class ClassProperty : IProperty /// /// The properties of the class property. /// - public required Dictionary Properties { get; set; } + public required IList Value { get; set; } /// public IProperty Clone() => new ClassProperty { Name = Name, PropertyType = PropertyType, - Properties = Properties.ToDictionary(p => p.Key, p => p.Value.Clone()) + Value = Value.Select(property => property.Clone()).ToList() }; + + /// + public IList GetProperties() => Value; + + /// + public T GetProperty(string name) where T : IProperty => throw new System.NotImplementedException(); + + /// + public bool TryGetProperty(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; + } } diff --git a/src/DotTiled/Model/Properties/ColorProperty.cs b/src/DotTiled/Model/Properties/ColorProperty.cs index aec43f6..7c4a132 100644 --- a/src/DotTiled/Model/Properties/ColorProperty.cs +++ b/src/DotTiled/Model/Properties/ColorProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents a color property. /// -public class ColorProperty : IProperty +public class ColorProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Model/Properties/FileProperty.cs b/src/DotTiled/Model/Properties/FileProperty.cs index 4ed8642..7c65121 100644 --- a/src/DotTiled/Model/Properties/FileProperty.cs +++ b/src/DotTiled/Model/Properties/FileProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents a file property. /// -public class FileProperty : IProperty +public class FileProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Model/Properties/FloatProperty.cs b/src/DotTiled/Model/Properties/FloatProperty.cs index 4c6b51f..57ba928 100644 --- a/src/DotTiled/Model/Properties/FloatProperty.cs +++ b/src/DotTiled/Model/Properties/FloatProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents a float property. /// -public class FloatProperty : IProperty +public class FloatProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Model/Properties/IHasProperties.cs b/src/DotTiled/Model/Properties/IHasProperties.cs new file mode 100644 index 0000000..28b8532 --- /dev/null +++ b/src/DotTiled/Model/Properties/IHasProperties.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; + +namespace DotTiled.Model; + +/// +/// Interface for objects that have properties attached to them. +/// +public interface IHasProperties +{ + /// + /// The properties attached to the object. + /// + IList GetProperties(); + + /// + /// Tries to get a property of the specified type with the specified name. + /// + /// The type of the property to get. + /// The name of the property to get. + /// The property with the specified name, if found. + /// True if a property with the specified name was found; otherwise, false. + bool TryGetProperty(string name, out T? property) where T : IProperty; + + /// + /// Gets a property of the specified type with the specified name. + /// + /// The type of the property to get. + /// The name of the property to get. + /// The property with the specified name. + T GetProperty(string name) where T : IProperty; +} + +/// +/// Base class for objects that have properties attached to them. +/// +public abstract class HasPropertiesBase : IHasProperties +{ + /// + public abstract IList GetProperties(); + + /// + public T GetProperty(string name) where T : IProperty => throw new System.NotImplementedException(); + + /// + public bool TryGetProperty(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; + } +} diff --git a/src/DotTiled/Model/Properties/IProperty.cs b/src/DotTiled/Model/Properties/IProperty.cs index 262ee09..d2d98e8 100644 --- a/src/DotTiled/Model/Properties/IProperty.cs +++ b/src/DotTiled/Model/Properties/IProperty.cs @@ -22,3 +22,15 @@ public interface IProperty /// An identical, but non-reference-equal, instance of the same property. IProperty Clone(); } + +/// +/// Interface for properties that can be attached to objects, tiles, tilesets, maps etc. +/// +/// The type of the property value. +public interface IProperty : IProperty +{ + /// + /// The value of the property. + /// + public T Value { get; set; } +} diff --git a/src/DotTiled/Model/Properties/IntProperty.cs b/src/DotTiled/Model/Properties/IntProperty.cs index 29f7a1d..27a7afc 100644 --- a/src/DotTiled/Model/Properties/IntProperty.cs +++ b/src/DotTiled/Model/Properties/IntProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents an integer property. /// -public class IntProperty : IProperty +public class IntProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Model/Properties/ObjectProperty.cs b/src/DotTiled/Model/Properties/ObjectProperty.cs index 04b15ba..de8a1e0 100644 --- a/src/DotTiled/Model/Properties/ObjectProperty.cs +++ b/src/DotTiled/Model/Properties/ObjectProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents an object property. /// -public class ObjectProperty : IProperty +public class ObjectProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Model/Properties/StringProperty.cs b/src/DotTiled/Model/Properties/StringProperty.cs index 49d7aec..0c20b29 100644 --- a/src/DotTiled/Model/Properties/StringProperty.cs +++ b/src/DotTiled/Model/Properties/StringProperty.cs @@ -3,7 +3,7 @@ namespace DotTiled.Model; /// /// Represents a string property. /// -public class StringProperty : IProperty +public class StringProperty : IProperty { /// public required string Name { get; set; } diff --git a/src/DotTiled/Serialization/Helpers.cs b/src/DotTiled/Serialization/Helpers.cs index c6cdefe..0028650 100644 --- a/src/DotTiled/Serialization/Helpers.cs +++ b/src/DotTiled/Serialization/Helpers.cs @@ -73,6 +73,9 @@ internal static partial class Helpers }; } + internal static List CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) => + customClassDefinition.Members.Select(x => x.Clone()).ToList(); + internal static Dictionary MergeProperties(Dictionary? baseProperties, Dictionary? overrideProperties) { if (baseProperties is null) @@ -93,7 +96,7 @@ internal static partial class Helpers { if (value is ClassProperty classProp) { - ((ClassProperty)baseProp).Properties = MergeProperties(((ClassProperty)baseProp).Properties, classProp.Properties); + ((ClassProperty)baseProp).Value = MergePropertiesList(((ClassProperty)baseProp).Value, classProp.Value); } else { @@ -105,6 +108,48 @@ internal static partial class Helpers return result; } + internal static IList MergePropertiesList(IList? baseProperties, IList? 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 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(ref T? field, T value, string fieldName) { if (field is not null) diff --git a/src/DotTiled/Serialization/Tmj/Tmj.Map.cs b/src/DotTiled/Serialization/Tmj/Tmj.Map.cs index 47dde3f..dd1a012 100644 --- a/src/DotTiled/Serialization/Tmj/Tmj.Map.cs +++ b/src/DotTiled/Serialization/Tmj/Tmj.Map.cs @@ -58,7 +58,7 @@ internal partial class Tmj var nextObjectID = element.GetRequiredProperty("nextobjectid"); var infinite = element.GetOptionalProperty("infinite", false); - var properties = element.GetOptionalPropertyCustom?>("properties", el => ReadProperties(el, customTypeDefinitions), null); + var properties = element.GetOptionalPropertyCustom?>("properties", el => ReadPropertiesList(el, customTypeDefinitions), null); List layers = element.GetOptionalPropertyCustom>("layers", e => e.GetValueAsList(el => ReadLayer(el, externalTemplateResolver, customTypeDefinitions)), []); List tilesets = element.GetOptionalPropertyCustom>("tilesets", e => e.GetValueAsList(el => ReadTileset(el, externalTilesetResolver, externalTemplateResolver, customTypeDefinitions)), []); @@ -84,7 +84,7 @@ internal partial class Tmj NextLayerID = nextLayerID, NextObjectID = nextObjectID, Infinite = infinite, - Properties = properties, + Properties = properties ?? [], Tilesets = tilesets, Layers = layers }; diff --git a/src/DotTiled/Serialization/Tmj/Tmj.Properties.cs b/src/DotTiled/Serialization/Tmj/Tmj.Properties.cs index 0c35bbe..5b25429 100644 --- a/src/DotTiled/Serialization/Tmj/Tmj.Properties.cs +++ b/src/DotTiled/Serialization/Tmj/Tmj.Properties.cs @@ -43,6 +43,41 @@ internal partial class Tmj return property!; }).ToDictionary(p => p.Name); + internal static List ReadPropertiesList( + JsonElement element, + IReadOnlyCollection customTypeDefinitions) => + element.GetValueAsList(e => + { + var name = e.GetRequiredProperty("name"); + var type = e.GetOptionalPropertyParseable("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("value") }, + PropertyType.Int => new IntProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Float => new FloatProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Bool => new BoolProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = e.GetRequiredPropertyParseable("value") }, + PropertyType.File => new FileProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Object => new ObjectProperty { Name = name, Value = e.GetRequiredProperty("value") }, + PropertyType.Class => ReadClassProperty(e, customTypeDefinitions), + _ => throw new JsonException("Invalid property type") + }; + + return property!; + }); + internal static ClassProperty ReadClassProperty( JsonElement element, IReadOnlyCollection customTypeDefinitions) @@ -54,28 +89,28 @@ internal partial class Tmj if (customTypeDef is CustomClassDefinition ccd) { - var propsInType = CreateInstanceOfCustomClass(ccd); - var props = element.GetOptionalPropertyCustom>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); + var propsInType = Helpers.CreateInstanceOfCustomClass(ccd); + var props = element.GetOptionalPropertyCustom>("value", el => ReadCustomClassProperties(el, ccd, customTypeDefinitions), []); - var mergedProps = Helpers.MergeProperties(propsInType, props); + var mergedProps = Helpers.MergePropertiesList(propsInType, props); return new ClassProperty { Name = name, PropertyType = propertyType, - Properties = mergedProps + Value = props }; } throw new JsonException($"Unknown custom class '{propertyType}'."); } - internal static Dictionary ReadCustomClassProperties( + internal static List ReadCustomClassProperties( JsonElement element, CustomClassDefinition customClassDefinition, IReadOnlyCollection customTypeDefinitions) { - Dictionary resultingProps = []; + List resultingProps = Helpers.CreateInstanceOfCustomClass(customClassDefinition); foreach (var prop in customClassDefinition.Members) { @@ -95,12 +130,9 @@ internal partial class Tmj _ => throw new JsonException("Invalid property type") }; - resultingProps[prop.Name] = property; + Helpers.ReplacePropertyInList(resultingProps, property); } return resultingProps; } - - internal static Dictionary CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) => - customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone()); } diff --git a/src/DotTiled/Serialization/Tmx/Tmx.Map.cs b/src/DotTiled/Serialization/Tmx/Tmx.Map.cs index 1987bb1..6dd5ea5 100644 --- a/src/DotTiled/Serialization/Tmx/Tmx.Map.cs +++ b/src/DotTiled/Serialization/Tmx/Tmx.Map.cs @@ -61,7 +61,7 @@ internal partial class Tmx var infinite = (reader.GetOptionalAttributeParseable("infinite") ?? 0) == 1; // At most one of - Dictionary? properties = null; + List? properties = null; // Any number of List layers = []; @@ -69,7 +69,7 @@ internal partial class Tmx 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)), "layer" => () => layers.Add(ReadTileLayer(r, infinite, customTypeDefinitions)), "objectgroup" => () => layers.Add(ReadObjectLayer(r, externalTemplateResolver, customTypeDefinitions)), @@ -99,7 +99,7 @@ internal partial class Tmx NextLayerID = nextLayerID, NextObjectID = nextObjectID, Infinite = infinite, - Properties = properties, + Properties = properties ?? [], Tilesets = tilesets, Layers = layers }; diff --git a/src/DotTiled/Serialization/Tmx/Tmx.Properties.cs b/src/DotTiled/Serialization/Tmx/Tmx.Properties.cs index 9a748d6..98ee546 100644 --- a/src/DotTiled/Serialization/Tmx/Tmx.Properties.cs +++ b/src/DotTiled/Serialization/Tmx/Tmx.Properties.cs @@ -43,6 +43,42 @@ internal partial class Tmx }).ToDictionary(x => x.name, x => x.property); } + internal static List ReadPropertiesList( + XmlReader reader, + IReadOnlyCollection customTypeDefinitions) + { + return reader.ReadList("properties", "property", (r) => + { + var name = r.GetRequiredAttribute("name"); + var type = r.GetOptionalAttributeEnum("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("value") }, + PropertyType.Float => new FloatProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Bool => new BoolProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Color => new ColorProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.File => new FileProperty { Name = name, Value = r.GetRequiredAttribute("value") }, + PropertyType.Object => new ObjectProperty { Name = name, Value = r.GetRequiredAttributeParseable("value") }, + PropertyType.Class => ReadClassProperty(r, customTypeDefinitions), + _ => throw new XmlException("Invalid property type") + }; + return property; + }); + } + internal static ClassProperty ReadClassProperty( XmlReader reader, IReadOnlyCollection customTypeDefinitions) @@ -54,18 +90,14 @@ internal partial class Tmx if (customTypeDef is CustomClassDefinition ccd) { reader.ReadStartElement("property"); - var propsInType = CreateInstanceOfCustomClass(ccd); - var props = ReadProperties(reader, customTypeDefinitions); - - var mergedProps = Helpers.MergeProperties(propsInType, props); + var propsInType = Helpers.CreateInstanceOfCustomClass(ccd); + var props = ReadPropertiesList(reader, customTypeDefinitions); + var mergedProps = Helpers.MergePropertiesList(propsInType, props); 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}"); } - - internal static Dictionary CreateInstanceOfCustomClass(CustomClassDefinition customClassDefinition) => - customClassDefinition.Members.ToDictionary(m => m.Name, m => m.Clone()); }