diff --git a/src/DotTiled.Tests/Serialization/MapReaderTests.cs b/src/DotTiled.Tests/Serialization/MapReaderTests.cs new file mode 100644 index 0000000..c6e3ec7 --- /dev/null +++ b/src/DotTiled.Tests/Serialization/MapReaderTests.cs @@ -0,0 +1,50 @@ +using DotTiled.Model; +using DotTiled.Serialization; + +namespace DotTiled.Tests; + +public partial class MapReaderTests +{ + public static IEnumerable Maps => TestData.MapTests; + [Theory] + [MemberData(nameof(Maps))] + public void MapReaderReadMap_ValidFilesExternalTilesetsAndTemplates_ReturnsMapThatEqualsExpected( + string testDataFile, + Func expectedMap, + IReadOnlyCollection 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); + } + } +} diff --git a/src/DotTiled/Serialization/Helpers.cs b/src/DotTiled/Serialization/Helpers.cs index 1e95695..d47d627 100644 --- a/src/DotTiled/Serialization/Helpers.cs +++ b/src/DotTiled/Serialization/Helpers.cs @@ -151,4 +151,6 @@ internal static partial class Helpers field = value; counter++; } + + internal static bool StringIsXml(string s) => s.StartsWith(" +/// Reads a map from a string, regardless of format. +/// +public class MapReader : IMapReader +{ + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + private readonly Func _customTypeResolver; + + private readonly StringReader? _mapStringReader; + private readonly XmlReader? _xmlReader; + private readonly IMapReader _mapReader; + private bool disposedValue; + + /// + /// Constructs a new , capable of reading a map from a string, regardless of format. + /// + /// The string containing the map data. + /// A function that resolves external tilesets given their source. + /// A function that resolves external templates given their source. + /// A function that resolves custom types given their source. + /// Thrown when any of the arguments are null. + public MapReader( + string map, + Func externalTilesetResolver, + Func externalTemplateResolver, + Func 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); + } + } + + /// + public Map ReadMap() => _mapReader.ReadMap(); + + /// + 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); + // } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/DotTiled/Serialization/TemplateReader.cs b/src/DotTiled/Serialization/TemplateReader.cs new file mode 100644 index 0000000..f354c21 --- /dev/null +++ b/src/DotTiled/Serialization/TemplateReader.cs @@ -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; + +/// +/// Reads a template from a string, regardless of format. +/// +public class TemplateReader : ITemplateReader +{ + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + private readonly Func _customTypeResolver; + + private readonly StringReader? _templateStringReader; + private readonly XmlReader? _xmlReader; + private readonly ITemplateReader _templateReader; + private bool disposedValue; + + /// + /// Constructs a new , capable of reading a template from a string, regardless of format. + /// + /// The string containing the template data. + /// A function that resolves external tilesets given their source. + /// A function that resolves external templates given their source. + /// A function that resolves custom types given their source. + /// Thrown when any of the arguments are null. + public TemplateReader( + string template, + Func externalTilesetResolver, + Func externalTemplateResolver, + Func 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); + } + } + + /// + public Template ReadTemplate() => _templateReader.ReadTemplate(); + + /// + 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); + // } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/DotTiled/Serialization/TilesetReader.cs b/src/DotTiled/Serialization/TilesetReader.cs new file mode 100644 index 0000000..55f0099 --- /dev/null +++ b/src/DotTiled/Serialization/TilesetReader.cs @@ -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; + +/// +/// Reads a tileset from a string, regardless of format. +/// +public class TilesetReader : ITilesetReader +{ + // External resolvers + private readonly Func _externalTilesetResolver; + private readonly Func _externalTemplateResolver; + private readonly Func _customTypeResolver; + + private readonly StringReader? _tilesetStringReader; + private readonly XmlReader? _xmlReader; + private readonly ITilesetReader _tilesetReader; + private bool disposedValue; + + /// + /// Constructs a new , capable of reading a tileset from a string, regardless of format. + /// + /// The string containing the tileset data. + /// A function that resolves external tilesets given their source. + /// A function that resolves external templates given their source. + /// A function that resolves custom types given their source. + /// Thrown when any of the arguments are null. + public TilesetReader( + string tileset, + Func externalTilesetResolver, + Func externalTemplateResolver, + Func 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); + } + } + + /// + public Tileset ReadTileset() => _tilesetReader.ReadTileset(); + + /// + 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); + // } + + /// + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +}