Add agnostic readers

This commit is contained in:
Daniel Cronqvist 2024-08-28 19:38:38 +02:00
parent 8f9fa3a315
commit aa8b390442
5 changed files with 328 additions and 0 deletions

View file

@ -0,0 +1,50 @@
using DotTiled.Model;
using DotTiled.Serialization;
namespace DotTiled.Tests;
public partial class MapReaderTests
{
public static IEnumerable<object[]> Maps => TestData.MapTests;
[Theory]
[MemberData(nameof(Maps))]
public void MapReaderReadMap_ValidFilesExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected(
string testDataFile,
Func<string, Map> expectedMap,
IReadOnlyCollection<ICustomTypeDefinition> customTypeDefinitions)
{
// Arrange
string[] fileFormats = [".tmx", ".tmj"];
foreach (var fileFormat in fileFormats)
{
var testDataFileWithFormat = testDataFile + fileFormat;
var fileDir = Path.GetDirectoryName(testDataFileWithFormat);
var mapString = TestData.GetRawStringFor(testDataFileWithFormat);
Template ResolveTemplate(string source)
{
var templateString = TestData.GetRawStringFor($"{fileDir}/{source}");
using var templateReader = new TemplateReader(templateString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return templateReader.ReadTemplate();
}
Tileset ResolveTileset(string source)
{
var tilesetString = TestData.GetRawStringFor($"{fileDir}/{source}");
using var tilesetReader = new TilesetReader(tilesetString, ResolveTileset, ResolveTemplate, ResolveCustomType);
return tilesetReader.ReadTileset();
}
ICustomTypeDefinition ResolveCustomType(string name)
{
return customTypeDefinitions.FirstOrDefault(ctd => ctd.Name == name)!;
}
using var mapReader = new MapReader(mapString, ResolveTileset, ResolveTemplate, ResolveCustomType);
// Act
var map = mapReader.ReadMap();
// Assert
Assert.NotNull(map);
DotTiledAssert.AssertMap(expectedMap(fileFormat[1..]), map);
}
}
}

View file

@ -151,4 +151,6 @@ internal static partial class Helpers
field = value;
counter++;
}
internal static bool StringIsXml(string s) => s.StartsWith("<?xml", StringComparison.InvariantCulture);
}

View file

@ -0,0 +1,92 @@
using System;
using System.IO;
using System.Xml;
using DotTiled.Model;
using DotTiled.Serialization.Tmj;
using DotTiled.Serialization.Tmx;
namespace DotTiled.Serialization;
/// <summary>
/// Reads a map from a string, regardless of format.
/// </summary>
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 StringReader? _mapStringReader;
private readonly XmlReader? _xmlReader;
private readonly IMapReader _mapReader;
private bool disposedValue;
/// <summary>
/// Constructs a new <see cref="MapReader"/>, capable of reading a map from a string, regardless of format.
/// </summary>
/// <param name="map">The string containing the map data.</param>
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
/// <param name="customTypeResolver">A function that resolves custom types given their source.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
public MapReader(
string map,
Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver)
{
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
// Prepare reader
if (Helpers.StringIsXml(map))
{
_mapStringReader = new StringReader(map);
_xmlReader = XmlReader.Create(_mapStringReader);
_mapReader = new TmxMapReader(_xmlReader, _externalTilesetResolver, _externalTemplateResolver, _customTypeResolver);
}
else
{
_mapReader = new TmjMapReader(map, _externalTilesetResolver, _externalTemplateResolver, _customTypeResolver);
}
}
/// <inheritdoc />
public Map ReadMap() => _mapReader.ReadMap();
/// <inheritdoc />
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
_mapStringReader?.Dispose();
_xmlReader?.Dispose();
_mapReader.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~MapReader()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View file

@ -0,0 +1,92 @@
using System;
using System.IO;
using System.Xml;
using DotTiled.Model;
using DotTiled.Serialization.Tmj;
using DotTiled.Serialization.Tmx;
namespace DotTiled.Serialization;
/// <summary>
/// Reads a template from a string, regardless of format.
/// </summary>
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 StringReader? _templateStringReader;
private readonly XmlReader? _xmlReader;
private readonly ITemplateReader _templateReader;
private bool disposedValue;
/// <summary>
/// Constructs a new <see cref="TemplateReader"/>, capable of reading a template from a string, regardless of format.
/// </summary>
/// <param name="template">The string containing the template data.</param>
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
/// <param name="customTypeResolver">A function that resolves custom types given their source.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
public TemplateReader(
string template,
Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver)
{
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
// Prepare reader
if (Helpers.StringIsXml(template))
{
_templateStringReader = new StringReader(template);
_xmlReader = XmlReader.Create(_templateStringReader);
_templateReader = new TxTemplateReader(_xmlReader, _externalTilesetResolver, _externalTemplateResolver, _customTypeResolver);
}
else
{
_templateReader = new TjTemplateReader(template, _externalTilesetResolver, _externalTemplateResolver, _customTypeResolver);
}
}
/// <inheritdoc />
public Template ReadTemplate() => _templateReader.ReadTemplate();
/// <inheritdoc />
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
_templateStringReader?.Dispose();
_xmlReader?.Dispose();
_templateReader.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~MapReader()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}

View file

@ -0,0 +1,92 @@
using System;
using System.IO;
using System.Xml;
using DotTiled.Model;
using DotTiled.Serialization.Tmj;
using DotTiled.Serialization.Tmx;
namespace DotTiled.Serialization;
/// <summary>
/// Reads a tileset from a string, regardless of format.
/// </summary>
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 StringReader? _tilesetStringReader;
private readonly XmlReader? _xmlReader;
private readonly ITilesetReader _tilesetReader;
private bool disposedValue;
/// <summary>
/// Constructs a new <see cref="TilesetReader"/>, capable of reading a tileset from a string, regardless of format.
/// </summary>
/// <param name="tileset">The string containing the tileset data.</param>
/// <param name="externalTilesetResolver">A function that resolves external tilesets given their source.</param>
/// <param name="externalTemplateResolver">A function that resolves external templates given their source.</param>
/// <param name="customTypeResolver">A function that resolves custom types given their source.</param>
/// <exception cref="ArgumentNullException">Thrown when any of the arguments are null.</exception>
public TilesetReader(
string tileset,
Func<string, Tileset> externalTilesetResolver,
Func<string, Template> externalTemplateResolver,
Func<string, ICustomTypeDefinition> customTypeResolver)
{
_externalTilesetResolver = externalTilesetResolver ?? throw new ArgumentNullException(nameof(externalTilesetResolver));
_externalTemplateResolver = externalTemplateResolver ?? throw new ArgumentNullException(nameof(externalTemplateResolver));
_customTypeResolver = customTypeResolver ?? throw new ArgumentNullException(nameof(customTypeResolver));
// Prepare reader
if (Helpers.StringIsXml(tileset))
{
_tilesetStringReader = new StringReader(tileset);
_xmlReader = XmlReader.Create(_tilesetStringReader);
_tilesetReader = new TsxTilesetReader(_xmlReader, _externalTilesetResolver, _externalTemplateResolver, _customTypeResolver);
}
else
{
_tilesetReader = new TsjTilesetReader(tileset, _externalTilesetResolver, _externalTemplateResolver, _customTypeResolver);
}
}
/// <inheritdoc />
public Tileset ReadTileset() => _tilesetReader.ReadTileset();
/// <inheritdoc />
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
_tilesetStringReader?.Dispose();
_xmlReader?.Dispose();
_tilesetReader.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~MapReader()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
/// <inheritdoc />
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}