mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
Merge pull request #57 from dcronqvist/custom-types-not-required
Make custom types optional
This commit is contained in:
commit
6deb28c1ce
22 changed files with 193 additions and 60 deletions
|
@ -73,7 +73,10 @@ In addition to these primitive property types, [Tiled also supports more complex
|
|||
Tiled allows you to define custom property types that can be used in your maps. These custom property types can be of type `class` or `enum`. DotTiled supports custom property types by allowing you to define the equivalent in C#. This section will guide you through how to define custom property types in DotTiled and how to map properties in loaded maps to C# classes or enums.
|
||||
|
||||
> [!NOTE]
|
||||
> In the future, DotTiled could provide a way to configure the use of custom property types such that they aren't necessary to be defined, given that you have set the `Resolve object types and properties` setting in Tiled.
|
||||
> While custom types are powerful, they will incur a bit of overhead as you attempt to sync them between Tiled and DotTiled. Defining custom types is recommended, but not necessary for simple use cases as Tiled supports arbitrary strings as classes.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> If you choose to use custom types in your maps, but don't define them properly in DotTiled, you may get inconsistencies between the map in Tiled and the loaded map with DotTiled. If you still want to use custom types in Tiled without having to define them in DotTiled, it is recommended to set the `Resolve object types and properties` setting in Tiled to `true`. This will make Tiled resolve the custom types for you, but it will still require you to define the custom types in DotTiled if you want to access the properties in a type-safe manner.
|
||||
|
||||
### Class properties
|
||||
|
||||
|
|
|
@ -73,12 +73,12 @@ public class Program
|
|||
return templateReader.ReadTemplate();
|
||||
}
|
||||
|
||||
private static ICustomTypeDefinition ResolveCustomType(string name)
|
||||
private static Optional<ICustomTypeDefinition> 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<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<ICustomTypeDefinition> 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<ICustomTypeDefinition>(ctd) : Optional<ICustomTypeDefinition>.Empty;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,7 +246,7 @@ public class LoaderTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ThrowsException()
|
||||
public void LoadMap_MapHasClassAndLoaderHasNoCustomTypes_ReturnsMapWithEmptyProperties()
|
||||
{
|
||||
// Arrange
|
||||
var resourceReader = Substitute.For<IResourceReader>();
|
||||
|
@ -270,8 +270,11 @@ public class LoaderTests
|
|||
var customTypeDefinitions = Enumerable.Empty<ICustomTypeDefinition>();
|
||||
var loader = new Loader(resourceReader, resourceCache, customTypeDefinitions);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<KeyNotFoundException>(() => loader.LoadMap("map.tmx"));
|
||||
// Act
|
||||
var result = loader.LoadMap("map.tmx");
|
||||
|
||||
// Assert
|
||||
DotTiledAssert.AssertProperties([], result.Properties);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -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<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);
|
||||
|
||||
|
|
|
@ -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<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);
|
||||
|
||||
|
|
|
@ -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<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);
|
||||
|
||||
|
|
|
@ -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))
|
||||
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<IProperty> CreateInstanceOfCustomClass(
|
||||
CustomClassDefinition customClassDefinition,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> 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)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ public class Loader
|
|||
{
|
||||
private readonly IResourceReader _resourceReader;
|
||||
private readonly IResourceCache _resourceCache;
|
||||
private readonly IDictionary<string, ICustomTypeDefinition> _customTypeDefinitions;
|
||||
private readonly Dictionary<string, ICustomTypeDefinition> _customTypeDefinitions;
|
||||
|
||||
/// <summary>
|
||||
/// 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();
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ public class MapReader : IMapReader
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
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 XmlReader _xmlReader;
|
||||
|
@ -33,7 +33,7 @@ public class MapReader : IMapReader
|
|||
string map,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
|
|
|
@ -14,7 +14,7 @@ public class TemplateReader : ITemplateReader
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
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 XmlReader _xmlReader;
|
||||
|
@ -33,7 +33,7 @@ public class TemplateReader : ITemplateReader
|
|||
string template,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
|
|
|
@ -14,7 +14,7 @@ public class TilesetReader : ITilesetReader
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
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 XmlReader _xmlReader;
|
||||
|
@ -33,7 +33,7 @@ public class TilesetReader : ITilesetReader
|
|||
string tileset,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
|
||||
|
|
|
@ -19,7 +19,7 @@ public class TjTemplateReader : TmjReaderBase, ITemplateReader
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ public class TmjMapReader : TmjReaderBase, IMapReader
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -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<string>("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<List<IProperty>>("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<List<IProperty>>("value", e => ReadPropertiesInsideClass(e, ccd)).GetValueOr([]);
|
||||
var mergedProps = Helpers.MergeProperties(propsInType, props);
|
||||
|
||||
return new ClassProperty
|
||||
{
|
||||
Name = name,
|
||||
PropertyType = propertyType,
|
||||
Value = mergedProps
|
||||
};
|
||||
}
|
||||
|
||||
internal List<IProperty> 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<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.File => new FileProperty { Name = prop.Name, Value = propElement.GetValueAs<string>() },
|
||||
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.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<string>("name");
|
||||
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,
|
||||
"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<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)
|
||||
{
|
||||
|
|
|
@ -13,7 +13,7 @@ public abstract partial class TmjReaderBase : IDisposable
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
/// <summary>
|
||||
/// The root element of the JSON document being read.
|
||||
|
@ -34,7 +34,7 @@ public abstract partial class TmjReaderBase : IDisposable
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
RootElement = JsonDocument.Parse(jsonString ?? throw new ArgumentNullException(nameof(jsonString))).RootElement;
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
|
|
|
@ -19,7 +19,7 @@ public class TsjTilesetReader : TmjReaderBase, ITilesetReader
|
|||
string jsonString,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
jsonString, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ public class TmxMapReader : TmxReaderBase, IMapReader
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
|
||||
|
@ -91,25 +92,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()
|
||||
|
@ -124,8 +135,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<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)
|
||||
{
|
||||
|
@ -169,6 +198,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}");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ public abstract partial class TmxReaderBase : IDisposable
|
|||
// External resolvers
|
||||
private readonly Func<string, Tileset> _externalTilesetResolver;
|
||||
private readonly Func<string, Template> _externalTemplateResolver;
|
||||
private readonly Func<string, ICustomTypeDefinition> _customTypeResolver;
|
||||
private readonly Func<string, Optional<ICustomTypeDefinition>> _customTypeResolver;
|
||||
|
||||
private readonly XmlReader _reader;
|
||||
private bool disposedValue;
|
||||
|
@ -28,7 +28,7 @@ public abstract partial class TmxReaderBase : IDisposable
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver)
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver)
|
||||
{
|
||||
_reader = reader ?? throw new ArgumentNullException(nameof(reader));
|
||||
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
|
||||
|
|
|
@ -16,7 +16,7 @@ public class TsxTilesetReader : TmxReaderBase, ITilesetReader
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ public class TxTemplateReader : TmxReaderBase, ITemplateReader
|
|||
XmlReader reader,
|
||||
Func<string, Tileset> externalTilesetResolver,
|
||||
Func<string, Template> externalTemplateResolver,
|
||||
Func<string, ICustomTypeDefinition> customTypeResolver) : base(
|
||||
Func<string, Optional<ICustomTypeDefinition>> customTypeResolver) : base(
|
||||
reader, externalTilesetResolver, externalTemplateResolver, customTypeResolver)
|
||||
{ }
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue