Make custom types optional

This commit is contained in:
Daniel Cronqvist 2024-11-16 21:14:23 +01:00
parent 67876c6532
commit 8c9068cc97
21 changed files with 189 additions and 59 deletions

View file

@ -73,12 +73,12 @@ public class Program
return templateReader.ReadTemplate(); return templateReader.ReadTemplate();
} }
private static ICustomTypeDefinition ResolveCustomType(string name) private static Optional<ICustomTypeDefinition> ResolveCustomType(string name)
{ {
ICustomTypeDefinition[] allDefinedTypes = ICustomTypeDefinition[] allDefinedTypes =
[ [
new CustomClassDefinition() { Name = "a" }, 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<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty;
} }
} }

View file

@ -1,4 +1,3 @@
using System;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using DotTiled.Serialization; using DotTiled.Serialization;
@ -57,12 +56,12 @@ public partial class MapParser : Node2D
return templateReader.ReadTemplate(); return templateReader.ReadTemplate();
} }
private static ICustomTypeDefinition ResolveCustomType(string name) private static Optional<ICustomTypeDefinition> ResolveCustomType(string name)
{ {
ICustomTypeDefinition[] allDefinedTypes = ICustomTypeDefinition[] allDefinedTypes =
[ [
new CustomClassDefinition() { Name = "a" }, 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<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty;
} }
} }

View file

@ -246,7 +246,7 @@ public class LoaderTests
} }
[Fact] [Fact]
public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ThrowsException() public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ReturnsMapWithEmptyProperties()
{ {
// Arrange // Arrange
var resourceReader = Substitute.For<IResourceReader>(); var resourceReader = Substitute.For<IResourceReader>();
@ -270,8 +270,11 @@ public class LoaderTests
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>(); var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions); var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
// Act & Assert // Act
Assert.Throws<KeyNotFoundException>(() => loader.LoadMap("map.tmx")); var result = loader.LoadMap("map.tmx");
// Assert
DotTiledAssert.AssertProperties([], result.Properties);
} }
[Fact] [Fact]

View file

@ -32,9 +32,14 @@ public partial class MapReaderTests
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType); using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset(); return tilesetReader.ReadTileset();
} }
ICustomTypeDefinition ResolveCustomType(string name) Optional<ICustomTypeDefinition> ResolveCustomType(string name)
{ {
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd)
{
return new Optional<ICustomTypeDefinition>(ctd);
}
return Optional<ICustomTypeDefinition>.Empty;
} }
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType); using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);

View file

@ -28,9 +28,14 @@ public partial class TmjMapReaderTests
using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType); using var tilesetReader = new TsjTilesetReader(tilesetJson, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset(); return tilesetReader.ReadTileset();
} }
ICustomTypeDefinition ResolveCustomType(string name) Optional<ICustomTypeDefinition> ResolveCustomType(string name)
{ {
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd)
{
return new Optional<ICustomTypeDefinition>(ctd);
}
return Optional<ICustomTypeDefinition>.Empty;
} }
using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType); using var mapReader = new TmjMapReader(json, ResolveTileset, ResolveTemplate, ResolveCustomType);

View file

@ -28,9 +28,14 @@ public partial class TmxMapReaderTests
using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType); using var tilesetReader = new TsxTilesetReader(xmlTilesetReader, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset(); return tilesetReader.ReadTileset();
} }
ICustomTypeDefinition ResolveCustomType(string name) Optional<ICustomTypeDefinition> ResolveCustomType(string name)
{ {
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!; if (customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name) is ICustomTypeDefinition ctd)
{
return new Optional<ICustomTypeDefinition>(ctd);
}
return Optional<ICustomTypeDefinition>.Empty;
} }
using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType); using var mapReader = new TmxMapReader(reader, ResolveTileset, ResolveTemplate, ResolveCustomType);

View file

@ -86,13 +86,16 @@ internal static partial class Helpers
}; };
} }
internal static List<IProperty> ResolveClassProperties(string className, Func<string, ICustomTypeDefinition> customTypeResolver) internal static List<IProperty> ResolveClassProperties(string className, Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
if (string.IsNullOrWhiteSpace(className)) if (string.IsNullOrWhiteSpace(className))
return null; return null;
var customType = customTypeResolver(className) ?? throw new InvalidOperationException($"Could not resolve custom type '{className}'."); 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."); throw new InvalidOperationException($"Custom type '{className}' is not a class.");
return CreateInstanceOfCustomClass(ccd, customTypeResolver); return CreateInstanceOfCustomClass(ccd, customTypeResolver);
@ -100,17 +103,31 @@ internal static partial class Helpers
internal static List<IProperty> CreateInstanceOfCustomClass( internal static List<IProperty> CreateInstanceOfCustomClass(
CustomClassDefinition customClassDefinition, CustomClassDefinition customClassDefinition,
Func<string, ICustomTypeDefinition> customTypeResolver) Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
return customClassDefinition.Members.Select(x => return customClassDefinition.Members.Select(x =>
{ {
if (x is ClassProperty cp) if (x is ClassProperty cp)
{
var resolvedType = customTypeResolver(cp.PropertyType);
if (!resolvedType.HasValue)
{ {
return new ClassProperty return new ClassProperty
{ {
Name = cp.Name, Name = cp.Name,
PropertyType = cp.PropertyType, PropertyType = cp.PropertyType,
Value = CreateInstanceOfCustomClass((CustomClassDefinition)customTypeResolver(cp.PropertyType), customTypeResolver) 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(ccd, customTypeResolver)
}; };
} }

View file

@ -12,7 +12,7 @@ public class Loader
{ {
private readonly IResourceReader _resourceReader; private readonly IResourceReader _resourceReader;
private readonly IResourceCache _resourceCache; private readonly IResourceCache _resourceCache;
private readonly IDictionary<string, ICustomTypeDefinition> _customTypeDefinitions; private readonly Dictionary<string, ICustomTypeDefinition> _customTypeDefinitions;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="Loader"/> class with the given <paramref name="resourceReader"/>, <paramref name="resourceCache"/>, and <paramref name="customTypeDefinitions"/>. /// Initializes a new instance of the <see cref="Loader"/> class with the given <paramref name="resourceReader"/>, <paramref name="resourceCache"/>, and <paramref name="customTypeDefinitions"/>.
@ -114,5 +114,11 @@ public class Loader
return templateReader.ReadTemplate(); return templateReader.ReadTemplate();
}); });
private ICustomTypeDefinition CustomTypeResolver(string name) => _customTypeDefinitions[name]; private Optional<ICustomTypeDefinition> CustomTypeResolver(string name)
{
if (_customTypeDefinitions.TryGetValue(name, out var customTypeDefinition))
return new Optional<ICustomTypeDefinition>(customTypeDefinition);
return Optional<ICustomTypeDefinition>.Empty;
}
} }

View file

@ -14,7 +14,7 @@ public class MapReader : IMapReader
// External resolvers // External resolvers
private readonly Func<string, Tileset> _externalTilesetResolver; private readonly Func<string, Tileset> _externalTilesetResolver;
private readonly Func<string, Template> _externalTemplateResolver; private readonly Func<string, Template> _externalTemplateResolver;
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver; private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
private readonly StringReader _mapStringReader; private readonly StringReader _mapStringReader;
private readonly XmlReader _xmlReader; private readonly XmlReader _xmlReader;
@ -33,7 +33,7 @@ public class MapReader : IMapReader
string map, string map,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));

View file

@ -14,7 +14,7 @@ public class TemplateReader : ITemplateReader
// External resolvers // External resolvers
private readonly Func<string, Tileset> _externalTilesetResolver; private readonly Func<string, Tileset> _externalTilesetResolver;
private readonly Func<string, Template> _externalTemplateResolver; private readonly Func<string, Template> _externalTemplateResolver;
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver; private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
private readonly StringReader _templateStringReader; private readonly StringReader _templateStringReader;
private readonly XmlReader _xmlReader; private readonly XmlReader _xmlReader;
@ -33,7 +33,7 @@ public class TemplateReader : ITemplateReader
string template, string template,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));

View file

@ -14,7 +14,7 @@ public class TilesetReader : ITilesetReader
// External resolvers // External resolvers
private readonly Func<string, Tileset> _externalTilesetResolver; private readonly Func<string, Tileset> _externalTilesetResolver;
private readonly Func<string, Template> _externalTemplateResolver; private readonly Func<string, Template> _externalTemplateResolver;
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver; private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
private readonly StringReader _tilesetStringReader; private readonly StringReader _tilesetStringReader;
private readonly XmlReader _xmlReader; private readonly XmlReader _xmlReader;
@ -33,7 +33,7 @@ public class TilesetReader : ITilesetReader
string tileset, string tileset,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver)); _externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));

View file

@ -19,7 +19,7 @@ public class TjTemplateReader : TmjReaderBase, ITemplateReader
string jsonString, string jsonString,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) : base( Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
{ } { }

View file

@ -19,7 +19,7 @@ public class TmjMapReader : TmjReaderBase, IMapReader
string jsonString, string jsonString,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) : base( Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
{ } { }

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -63,8 +64,28 @@ public abstract partial class TmjReaderBase
var propertyType = element.GetRequiredProperty<string>("propertytype"); var propertyType = element.GetRequiredProperty<string>("propertytype");
var customTypeDef = _customTypeResolver(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 (!element.TryGetProperty("value", out var valueElement))
return new ClassProperty { Name = name, PropertyType = propertyType, Value = [] };
return new ClassProperty
{
Name = name,
PropertyType = propertyType,
Value = ReadPropertiesInsideClass(valueElement, new CustomClassDefinition
{
Name = propertyType,
Members = []
})
};
}
if (customTypeDef.Value is not CustomClassDefinition ccd)
throw new JsonException($"Custom type {propertyType} is not a class.");
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver);
var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]); var props = element.GetOptionalPropertyCustom<List<IProperty>>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]);
var mergedProps = Helpers.MergeProperties(propsInType, props); var mergedProps = Helpers.MergeProperties(propsInType, props);
@ -77,9 +98,6 @@ public abstract partial class TmjReaderBase
}; };
} }
throw new JsonException($"Unknown custom class '{propertyType}'.");
}
internal List<IProperty> ReadPropertiesInsideClass( internal List<IProperty> ReadPropertiesInsideClass(
JsonElement element, JsonElement element,
CustomClassDefinition customClassDefinition) CustomClassDefinition customClassDefinition)
@ -91,6 +109,33 @@ public abstract partial class TmjReaderBase
if (!element.TryGetProperty(prop.Name, out var propElement)) if (!element.TryGetProperty(prop.Name, out var propElement))
continue; 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 IProperty property = prop.Type switch
{ {
PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() }, PropertyType.String => new StringProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
@ -100,8 +145,8 @@ public abstract partial class TmjReaderBase
PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs<string>(), CultureInfo.InvariantCulture) }, PropertyType.Color => new ColorProperty { Name = prop.Name, Value = Color.Parse(propElement.GetValueAs<string>(), CultureInfo.InvariantCulture) },
PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() }, PropertyType.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs<uint>() }, PropertyType.Object => new ObjectProperty { Name = prop.Name, Value = propElement.GetValueAs<uint>() },
PropertyType.Class => new ClassProperty { Name = prop.Name, PropertyType = ((ClassProperty)prop).PropertyType, Value = ReadPropertiesInsideClass(propElement, (CustomClassDefinition)_customTypeResolver(((ClassProperty)prop).PropertyType)) },
PropertyType.Enum => ReadEnumProperty(propElement), PropertyType.Enum => ReadEnumProperty(propElement),
PropertyType.Class => throw new NotImplementedException("Class properties should be handled elsewhere"),
_ => throw new JsonException("Invalid property type") _ => throw new JsonException("Invalid property type")
}; };
@ -115,7 +160,7 @@ public abstract partial class TmjReaderBase
{ {
var name = element.GetRequiredProperty<string>("name"); var name = element.GetRequiredProperty<string>("name");
var propertyType = element.GetRequiredProperty<string>("propertytype"); var propertyType = element.GetRequiredProperty<string>("propertytype");
var typeInXml = element.GetOptionalPropertyParseable<PropertyType>("type", (s) => s switch var typeInJson = element.GetOptionalPropertyParseable<PropertyType>("type", (s) => s switch
{ {
"string" => PropertyType.String, "string" => PropertyType.String,
"int" => PropertyType.Int, "int" => PropertyType.Int,
@ -123,8 +168,24 @@ public abstract partial class TmjReaderBase
}).GetValueOr(PropertyType.String); }).GetValueOr(PropertyType.String);
var customTypeDef = _customTypeResolver(propertyType); var customTypeDef = _customTypeResolver(propertyType);
if (customTypeDef is not CustomEnumDefinition ced) if (!customTypeDef.HasValue)
throw new JsonException($"Unknown custom enum '{propertyType}'. Enums must be defined"); {
if (typeInJson == PropertyType.String)
{
var value = element.GetRequiredProperty<string>("value");
var values = value.Split(',').Select(v => v.Trim()).ToHashSet();
return new EnumProperty { Name = name, PropertyType = propertyType, Value = values };
}
else
{
var value = element.GetRequiredProperty<int>("value");
var values = new HashSet<string> { 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) if (ced.StorageType == CustomEnumStorageType.String)
{ {

View file

@ -13,7 +13,7 @@ public abstract partial class TmjReaderBase : IDisposable
// External resolvers // External resolvers
private readonly Func<string, Tileset> _externalTilesetResolver; private readonly Func<string, Tileset> _externalTilesetResolver;
private readonly Func<string, Template> _externalTemplateResolver; private readonly Func<string, Template> _externalTemplateResolver;
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver; private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
/// <summary> /// <summary>
/// The root element of the JSON document being read. /// The root element of the JSON document being read.
@ -34,7 +34,7 @@ public abstract partial class TmjReaderBase : IDisposable
string jsonString, string jsonString,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement; RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement;
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));

View file

@ -19,7 +19,7 @@ public class TsjTilesetReader : TmjReaderBase, ITilesetReader
string jsonString, string jsonString,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) : base( Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver) jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
{ } { }

View file

@ -16,7 +16,7 @@ public class TmxMapReader : TmxReaderBase, IMapReader
XmlReader reader, XmlReader reader,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) : base( Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
{ } { }

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Xml; using System.Xml;
@ -66,25 +67,35 @@ public abstract partial class TmxReaderBase
var propertyType = _reader.GetRequiredAttribute("propertytype"); var propertyType = _reader.GetRequiredAttribute("propertytype");
var customTypeDef = _customTypeResolver(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) if (!_reader.IsEmptyElement)
{ {
_reader.ReadStartElement("property"); _reader.ReadStartElement("property");
var props = ReadProperties();
_reader.ReadEndElement();
return new ClassProperty { Name = name, PropertyType = propertyType, Value = props };
}
return new ClassProperty { Name = name, PropertyType = propertyType, Value = [] };
}
if (customTypeDef.Value is not CustomClassDefinition ccd)
throw new XmlException($"Custom type {propertyType} is not a class.");
var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver); var propsInType = Helpers.CreateInstanceOfCustomClass(ccd, _customTypeResolver);
if (!_reader.IsEmptyElement)
{
_reader.ReadStartElement("property");
var props = ReadProperties(); var props = ReadProperties();
var mergedProps = Helpers.MergeProperties(propsInType, props); var mergedProps = Helpers.MergeProperties(propsInType, props);
_reader.ReadEndElement(); _reader.ReadEndElement();
return new ClassProperty { Name = name, PropertyType = propertyType, Value = mergedProps }; 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 };
}
}
throw new XmlException($"Unkonwn custom class definition: {propertyType}"); return new ClassProperty { Name = name, PropertyType = propertyType, Value = propsInType };
} }
internal EnumProperty ReadEnumProperty() internal EnumProperty ReadEnumProperty()
@ -99,8 +110,26 @@ public abstract partial class TmxReaderBase
}) ?? PropertyType.String; }) ?? PropertyType.String;
var customTypeDef = _customTypeResolver(propertyType); var customTypeDef = _customTypeResolver(propertyType);
if (customTypeDef is not CustomEnumDefinition ced) // If the custom enum definition is not found,
throw new XmlException($"Unknown custom enum definition: {propertyType}. Enums must be defined"); // 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<int>("value");
var values = new HashSet<string> { 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) 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}");
} }
} }

View file

@ -11,7 +11,7 @@ public abstract partial class TmxReaderBase : IDisposable
// External resolvers // External resolvers
private readonly Func<string, Tileset> _externalTilesetResolver; private readonly Func<string, Tileset> _externalTilesetResolver;
private readonly Func<string, Template> _externalTemplateResolver; private readonly Func<string, Template> _externalTemplateResolver;
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver; private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
private readonly XmlReader _reader; private readonly XmlReader _reader;
private bool disposedValue; private bool disposedValue;
@ -28,7 +28,7 @@ public abstract partial class TmxReaderBase : IDisposable
XmlReader reader, XmlReader reader,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
{ {
_reader = reader ?? throw new ArgumentNullException(nameof(reader)); _reader = reader ?? throw new ArgumentNullException(nameof(reader));
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver)); _externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));

View file

@ -16,7 +16,7 @@ public class TsxTilesetReader : TmxReaderBase, ITilesetReader
XmlReader reader, XmlReader reader,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) : base( Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
{ } { }

View file

@ -16,7 +16,7 @@ public class TxTemplateReader : TmxReaderBase, ITemplateReader
XmlReader reader, XmlReader reader,
Func<string, Tileset> externalTilesetResolver, Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver, Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver) : base( Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver) reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
{ } { }