diff --git a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs index 9dba3df..baf36ea 100644 --- a/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs +++ b/src/DotTiled.Examples/DotTiled.Example.Console/Program.cs @@ -73,12 +73,12 @@ public class Program return templateReader.ReadTemplate(); } - private static ICustomTypeDefinition ResolveCustomType(string name) + private static Optional ResolveCustomType(string name) { ICustomTypeDefinition[] allDefinedTypes = [ new CustomClassDefinition() { Name = "a" }, ]; - return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException(); + return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional(ctd) : Optional.Empty; } } diff --git a/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs index 5ad960b..8319d00 100644 --- a/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs +++ b/src/DotTiled.Examples/DotTiled.Example.Godot/MapParser.cs @@ -1,4 +1,3 @@ -using System; using System.Globalization; using System.Linq; using DotTiled.Serialization; @@ -57,12 +56,12 @@ public partial class MapParser : Node2D return templateReader.ReadTemplate(); } - private static ICustomTypeDefinition ResolveCustomType(string name) + private static Optional ResolveCustomType(string name) { ICustomTypeDefinition[] allDefinedTypes = [ new CustomClassDefinition() { Name = "a" }, ]; - return allDefinedTypes.FirstOrDefault(type => type.Name == name) ?? throw new InvalidOperationException(); + return allDefinedTypes.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd ? new Optional(ctd) : Optional.Empty; } } diff --git a/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs index d54ab9f..7edff12 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/LoaderTests.cs @@ -246,7 +246,7 @@ public class LoaderTests } [Fact] - public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ThrowsException() + public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ReturnsMapWithEmptyProperties() { // Arrange var resourceReader = Substitute.For(); @@ -270,8 +270,11 @@ public class LoaderTests var customTypeDefinitions = Enumerable.Empty(); var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions); - // Act & Assert - Assert.Throws(() => loader.LoadMap("map.tmx")); + // Act + var result = loader.LoadMap("map.tmx"); + + // Assert + DotTiledAssert.AssertProperties([], result.Properties); } [Fact] diff --git a/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs index 885f57e..dd6bcca 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/MapReaderTests.cs @@ -32,9 +32,14 @@ public partial class MapReaderTests using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); return tilesetReader.ReadTileset(); } - ICustomTypeDefinition ResolveCustomType(string name) + Optional ResolveCustomType(string name) { - return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; + if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd) + { + return new Optional(ctd); + } + + return Optional.Empty; } using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); diff --git a/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs index a896a48..aa289f6 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/Tmj/TmjMapReaderTests.cs @@ -28,9 +28,14 @@ public partial class TmjMapReaderTests using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType); return tilesetReader.ReadTileset(); } - ICustomTypeDefinition ResolveCustomType(string name) + Optional ResolveCustomType(string name) { - return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; + if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd) + { + return new Optional(ctd); + } + + return Optional.Empty; } using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType); diff --git a/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs b/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs index b6e5813..82dba07 100644 --- a/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs +++ b/src/DotTiled.Tests/UnitTests/Serialization/Tmx/TmxMapReaderTests.cs @@ -28,9 +28,14 @@ public partial class TmxMapReaderTests using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType); return tilesetReader.ReadTileset(); } - ICustomTypeDefinition ResolveCustomType(string name) + Optional ResolveCustomType(string name) { - return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; + if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd) + { + return new Optional(ctd); + } + + return Optional.Empty; } using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType); diff --git a/src/DotTiled/Serialization/Helpers.cs b/src/DotTiled/Serialization/Helpers.cs index e784b80..d716293 100644 --- a/src/DotTiled/Serialization/Helpers.cs +++ b/src/DotTiled/Serialization/Helpers.cs @@ -86,13 +86,16 @@ internal static partial class Helpers }; } - internal static List ResolveClassProperties(string className, Func customTypeResolver) + internal static List ResolveClassProperties(string className, Func> customTypeResolver) { if (string.IsNullOrWhiteSpace(className)) return null; var customType = customTypeResolver(className) ?? throw new InvalidOperationException($"Could not resolve custom type '{className}'."); - if (customType is not CustomClassDefinition ccd) + if (!customType.HasValue) + return null; + + if (customType.Value is not CustomClassDefinition ccd) throw new InvalidOperationException($"Custom type '{className}' is not a class."); return CreateInstanceOfCustomClass(ccd, customTypeResolver); @@ -100,17 +103,31 @@ internal static partial class Helpers internal static List CreateInstanceOfCustomClass( CustomClassDefinition customClassDefinition, - Func customTypeResolver) + Func> customTypeResolver) { return customClassDefinition.Members.Select(x => { if (x is ClassProperty cp) { + var resolvedType = customTypeResolver(cp.PropertyType); + if (!resolvedType.HasValue) + { + return new ClassProperty + { + Name = cp.Name, + PropertyType = cp.PropertyType, + Value = [] + }; + } + + if (resolvedType.Value is not CustomClassDefinition ccd) + throw new InvalidOperationException($"Custom type '{cp.PropertyType}' is not a class."); + return new ClassProperty { Name = cp.Name, PropertyType = cp.PropertyType, - Value = CreateInstanceOfCustomClass((CustomClassDefinition)customTypeResolver(cp.PropertyType), customTypeResolver) + Value = CreateInstanceOfCustomClass(ccd, customTypeResolver) }; } diff --git a/src/DotTiled/Serialization/Loader.cs b/src/DotTiled/Serialization/Loader.cs index 02e258c..dd4fc2a 100644 --- a/src/DotTiled/Serialization/Loader.cs +++ b/src/DotTiled/Serialization/Loader.cs @@ -12,7 +12,7 @@ public class Loader { private readonly IResourceReader _resourceReader; private readonly IResourceCache _resourceCache; - private readonly IDictionary _customTypeDefinitions; + private readonly Dictionary _customTypeDefinitions; /// /// Initializes a new instance of the class with the given , , and . @@ -114,5 +114,11 @@ public class Loader return templateReader.ReadTemplate(); }); - private ICustomTypeDefinition CustomTypeResolver(string name) => _customTypeDefinitions[name]; + private Optional CustomTypeResolver(string name) + { + if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition)) + return new Optional(customTypeDefinition); + + return Optional.Empty; + } } diff --git a/src/DotTiled/Serialization/MapReader.cs b/src/DotTiled/Serialization/MapReader.cs index d202e8f..5a6f6a7 100644 --- a/src/DotTiled/Serialization/MapReader.cs +++ b/src/DotTiled/Serialization/MapReader.cs @@ -14,7 +14,7 @@ public class MapReader : IMapReader // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly StringReader _mapStringReader; private readonly XmlReader _xmlReader; @@ -33,7 +33,7 @@ public class MapReader : IMapReader string map, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); diff --git a/src/DotTiled/Serialization/TemplateReader.cs b/src/DotTiled/Serialization/TemplateReader.cs index bf210c0..d44da3e 100644 --- a/src/DotTiled/Serialization/TemplateReader.cs +++ b/src/DotTiled/Serialization/TemplateReader.cs @@ -14,7 +14,7 @@ public class TemplateReader : ITemplateReader // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly StringReader _templateStringReader; private readonly XmlReader _xmlReader; @@ -33,7 +33,7 @@ public class TemplateReader : ITemplateReader string template, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); diff --git a/src/DotTiled/Serialization/TilesetReader.cs b/src/DotTiled/Serialization/TilesetReader.cs index 180d269..3d7c83a 100644 --- a/src/DotTiled/Serialization/TilesetReader.cs +++ b/src/DotTiled/Serialization/TilesetReader.cs @@ -14,7 +14,7 @@ public class TilesetReader : ITilesetReader // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly StringReader _tilesetStringReader; private readonly XmlReader _xmlReader; @@ -33,7 +33,7 @@ public class TilesetReader : ITilesetReader string tileset, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); diff --git a/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs b/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs index 792bb73..ef80970 100644 --- a/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs +++ b/src/DotTiled/Serialization/Tmj/TjTemplateReader.cs @@ -19,7 +19,7 @@ public class TjTemplateReader : TmjReaderBase, ITemplateReader string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmj/TmjMapReader.cs b/src/DotTiled/Serialization/Tmj/TmjMapReader.cs index 8ceb211..cffa036 100644 --- a/src/DotTiled/Serialization/Tmj/TmjMapReader.cs +++ b/src/DotTiled/Serialization/Tmj/TmjMapReader.cs @@ -19,7 +19,7 @@ public class TmjMapReader : TmjReaderBase, IMapReader string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs index b877382..1ffff1c 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.Properties.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Globalization; using System.Linq; @@ -63,21 +64,38 @@ public abstract partial class TmjReaderBase var propertyType = element.GetRequiredProperty("propertytype"); var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is CustomClassDefinition ccd) + // If the custom class definition is not found, + // we assume an empty class definition. + if (!customTypeDef.HasValue) { - var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); - var props = element.GetOptionalPropertyCustom>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]); - var mergedProps = Helpers.MergeProperties(propsInType, props); + if (!element.TryGetProperty("value", out var valueElement)) + return new ClassProperty { Name = name, PropertyType = propertyType, Value = [] }; return new ClassProperty { Name = name, PropertyType = propertyType, - Value = mergedProps + Value = ReadPropertiesInsideClass(valueElement, new CustomClassDefinition + { + Name = propertyType, + Members = [] + }) }; } - throw new JsonException($"Unknown custom class '{propertyType}'."); + if (customTypeDef.Value is not CustomClassDefinition ccd) + throw new JsonException($"Custom type {propertyType} is not a class."); + + var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); + var props = element.GetOptionalPropertyCustom>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]); + var mergedProps = Helpers.MergeProperties(propsInType, props); + + return new ClassProperty + { + Name = name, + PropertyType = propertyType, + Value = mergedProps + }; } internal List ReadPropertiesInsideClass( @@ -91,6 +109,33 @@ public abstract partial class TmjReaderBase if (!element.TryGetProperty(prop.Name, out var propElement)) continue; + if (prop is ClassProperty classProp) + { + var resolvedCustomType = _customTypeResolver(classProp.PropertyType); + if (!resolvedCustomType.HasValue) + { + resultingProps.Add(new ClassProperty + { + Name = classProp.Name, + PropertyType = classProp.PropertyType, + Value = [] + }); + continue; + } + + if (resolvedCustomType.Value is not CustomClassDefinition ccd) + throw new JsonException($"Custom type '{classProp.PropertyType}' is not a class."); + + var readProps = ReadPropertiesInsideClass(propElement, ccd); + resultingProps.Add(new ClassProperty + { + Name = classProp.Name, + PropertyType = classProp.PropertyType, + Value = readProps + }); + continue; + } + IProperty property = prop.Type switch { PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs() }, @@ -100,8 +145,8 @@ public abstract partial class TmjReaderBase PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs(), CultureInfo.InvariantCulture) }, PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs() }, PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs() }, - PropertyType.Class => new ClassProperty { Name = prop.Name, PropertyType = ((ClassProperty)prop).PropertyType, Value = ReadPropertiesInsideClass(propElement, (CustomClassDefinition)_customTypeResolver(((ClassProperty)prop).PropertyType)) }, PropertyType.Enum => ReadEnumProperty(propElement), + PropertyType.Class => throw new NotImplementedException("Class properties should be handled elsewhere"), _ => throw new JsonException("Invalid property type") }; @@ -115,7 +160,7 @@ public abstract partial class TmjReaderBase { var name = element.GetRequiredProperty("name"); var propertyType = element.GetRequiredProperty("propertytype"); - var typeInXml = element.GetOptionalPropertyParseable("type", (s) => s switch + var typeInJson = element.GetOptionalPropertyParseable("type", (s) => s switch { "string" => PropertyType.String, "int" => PropertyType.Int, @@ -123,8 +168,24 @@ public abstract partial class TmjReaderBase }).GetValueOr(PropertyType.String); var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is not CustomEnumDefinition ced) - throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined"); + if (!customTypeDef.HasValue) + { + if (typeInJson == PropertyType.String) + { + var value = element.GetRequiredProperty("value"); + var values = value.Split(',').Select(v => v.Trim()).ToHashSet(); + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + else + { + var value = element.GetRequiredProperty("value"); + var values = new HashSet { value.ToString(CultureInfo.InvariantCulture) }; + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + } + + if (customTypeDef.Value is not CustomEnumDefinition ced) + throw new JsonException($"Custom type '{propertyType}' is not an enum."); if (ced.StorageType == CustomEnumStorageType.String) { diff --git a/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs b/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs index f32100a..c63b913 100644 --- a/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs +++ b/src/DotTiled/Serialization/Tmj/TmjReaderBase.cs @@ -13,7 +13,7 @@ public abstract partial class TmjReaderBase : IDisposable // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; /// /// The root element of the JSON document being read. @@ -34,7 +34,7 @@ public abstract partial class TmjReaderBase : IDisposable string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement; _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); diff --git a/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs b/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs index 5816c8a..87f4b59 100644 --- a/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs +++ b/src/DotTiled/Serialization/Tmj/TsjTilesetReader.cs @@ -19,7 +19,7 @@ public class TsjTilesetReader : TmjReaderBase, ITilesetReader string jsonString, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmx/TmxMapReader.cs b/src/DotTiled/Serialization/Tmx/TmxMapReader.cs index 6029481..e993a4c 100644 --- a/src/DotTiled/Serialization/Tmx/TmxMapReader.cs +++ b/src/DotTiled/Serialization/Tmx/TmxMapReader.cs @@ -16,7 +16,7 @@ public class TmxMapReader : TmxReaderBase, IMapReader XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs index bfdb93d..4aec221 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.Properties.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Xml; @@ -66,25 +67,35 @@ public abstract partial class TmxReaderBase var propertyType = _reader.GetRequiredAttribute("propertytype"); var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is CustomClassDefinition ccd) + // If the custom class definition is not found, + // we assume an empty class definition. + if (!customTypeDef.HasValue) { if (!_reader.IsEmptyElement) { _reader.ReadStartElement("property"); - var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); var props = ReadProperties(); - var mergedProps = Helpers.MergeProperties(propsInType, props); _reader.ReadEndElement(); - return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps }; - } - else - { - var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); - return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType }; + return new ClassProperty { Name = name, PropertyType = propertyType, Value = props }; } + + return new ClassProperty { Name = name, PropertyType = propertyType, Value = [] }; } - throw new XmlException($"Unkonwn custom class definition: {propertyType}"); + if (customTypeDef.Value is not CustomClassDefinition ccd) + throw new XmlException($"Custom type {propertyType} is not a class."); + + var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); + if (!_reader.IsEmptyElement) + { + _reader.ReadStartElement("property"); + var props = ReadProperties(); + var mergedProps = Helpers.MergeProperties(propsInType, props); + _reader.ReadEndElement(); + return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps }; + } + + return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType }; } internal EnumProperty ReadEnumProperty() @@ -99,8 +110,26 @@ public abstract partial class TmxReaderBase }) ?? PropertyType.String; var customTypeDef = _customTypeResolver(propertyType); - if (customTypeDef is not CustomEnumDefinition ced) - throw new XmlException($"Unknown custom enum definition: {propertyType}. Enums must be defined"); + // If the custom enum definition is not found, + // we assume an empty enum definition. + if (!customTypeDef.HasValue) + { + if (typeInXml == PropertyType.String) + { + var value = _reader.GetRequiredAttribute("value"); + var values = value.Split(',').Select(v => v.Trim()).ToHashSet(); + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + else + { + var value = _reader.GetRequiredAttributeParseable("value"); + var values = new HashSet { value.ToString(CultureInfo.InvariantCulture) }; + return new EnumProperty { Name = name, PropertyType = propertyType, Value = values }; + } + } + + if (customTypeDef.Value is not CustomEnumDefinition ced) + throw new XmlException($"Custom defined type {propertyType} is not an enum."); if (ced.StorageType == CustomEnumStorageType.String) { @@ -144,6 +173,6 @@ public abstract partial class TmxReaderBase } } - throw new XmlException($"Unknown custom enum storage type: {ced.StorageType}"); + throw new XmlException($"Unable to read enum property {name} with type {propertyType}"); } } diff --git a/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs b/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs index e30af82..e3fe2d0 100644 --- a/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs +++ b/src/DotTiled/Serialization/Tmx/TmxReaderBase.cs @@ -11,7 +11,7 @@ public abstract partial class TmxReaderBase : IDisposable // External resolvers private readonly Func _externalTilesetResolver; private readonly Func _externalTemplateResolver; - private readonly Func _customTypeResolver; + private readonly Func> _customTypeResolver; private readonly XmlReader _reader; private bool disposedValue; @@ -28,7 +28,7 @@ public abstract partial class TmxReaderBase : IDisposable XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) + Func> customTypeResolver) { _reader = reader ?? throw new ArgumentNullException(nameof(reader)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); diff --git a/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs b/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs index f0dbcc9..195f767 100644 --- a/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs +++ b/src/DotTiled/Serialization/Tmx/TsxTilesetReader.cs @@ -16,7 +16,7 @@ public class TsxTilesetReader : TmxReaderBase, ITilesetReader XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { } diff --git a/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs b/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs index 72da9dc..8a9d3b1 100644 --- a/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs +++ b/src/DotTiled/Serialization/Tmx/TxTemplateReader.cs @@ -16,7 +16,7 @@ public class TxTemplateReader : TmxReaderBase, ITemplateReader XmlReader reader, Func externalTilesetResolver, Func externalTemplateResolver, - Func customTypeResolver) : base( + Func> customTypeResolver) : base( reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) { }