mirror of
https://github.com/dcronqvist/DotTiled.git
synced 2025-02-05 08:52:50 +02:00
First initial modelling, going to move serialization away from model
This commit is contained in:
parent
f0548060c0
commit
d2be83972d
10 changed files with 1302 additions and 0 deletions
8
.editorconfig
Normal file
8
.editorconfig
Normal file
|
@ -0,0 +1,8 @@
|
|||
root = true
|
||||
|
||||
[*.cs]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 2
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
|||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
.vscode/
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
|
27
DotTiled.Tests/DotTiled.Tests.csproj
Normal file
27
DotTiled.Tests/DotTiled.Tests.csproj
Normal file
|
@ -0,0 +1,27 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DotTiled\DotTiled.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
259
DotTiled.Tests/MapTests.cs
Normal file
259
DotTiled.Tests/MapTests.cs
Normal file
|
@ -0,0 +1,259 @@
|
|||
using System.Text;
|
||||
using System.Xml.Serialization;
|
||||
using DotTiled;
|
||||
|
||||
namespace DotTiled.Tests;
|
||||
|
||||
public class MapTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReadXml_Always_SetsRequiredAttributes()
|
||||
{
|
||||
// Arrange
|
||||
var xml =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map
|
||||
version="1.2"
|
||||
class="class"
|
||||
orientation="orthogonal"
|
||||
renderorder="right-down"
|
||||
compressionlevel="5"
|
||||
width="10"
|
||||
height="10"
|
||||
tilewidth="32"
|
||||
tileheight="32"
|
||||
parallaxoriginx="0.5"
|
||||
parallaxoriginy="0.5"
|
||||
nextlayerid="1"
|
||||
nextobjectid="1"
|
||||
infinite="1"
|
||||
>
|
||||
</map>
|
||||
""";
|
||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
||||
|
||||
// Act
|
||||
var map = Map.LoadFromStream(xmlStream);
|
||||
|
||||
// Assert
|
||||
// Assert all required properties are set
|
||||
Assert.Equal("1.2", map.Version);
|
||||
Assert.Equal("class", map.Class);
|
||||
Assert.Equal(Orientation.Orthogonal, map.Orientation);
|
||||
Assert.Equal(RenderOrder.RightDown, map.RenderOrder);
|
||||
Assert.Equal(5, map.CompressionLevel);
|
||||
Assert.Equal(10u, map.Width);
|
||||
Assert.Equal(10u, map.Height);
|
||||
Assert.Equal(32u, map.TileWidth);
|
||||
Assert.Equal(32u, map.TileHeight);
|
||||
Assert.Equal(0.5f, map.ParallaxOriginX);
|
||||
Assert.Equal(0.5f, map.ParallaxOriginY);
|
||||
Assert.Equal(1u, map.NextLayerId);
|
||||
Assert.Equal(1u, map.NextObjectId);
|
||||
Assert.True(map.Infinite);
|
||||
|
||||
// Assert all optional properties are set to their default values
|
||||
Assert.Null(map.TiledVersion);
|
||||
Assert.Null(map.HexSideLength);
|
||||
Assert.Null(map.StaggerAxis);
|
||||
Assert.Null(map.StaggerIndex);
|
||||
Assert.Null(map.BackgroundColor);
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> ColorData =>
|
||||
new List<object[]>
|
||||
{
|
||||
new object[] { "#ff0000", new TiledColor { R = 255, G = 0, B = 0, A = 255 } },
|
||||
new object[] { "#00ff00", new TiledColor { R = 0, G = 255, B = 0, A = 255 } },
|
||||
new object[] { "#0000ff", new TiledColor { R = 0, G = 0, B = 255, A = 255 } },
|
||||
new object[] { "#ffffff", new TiledColor { R = 255, G = 255, B = 255, A = 255 } },
|
||||
new object[] { "#000000", new TiledColor { R = 0, G = 0, B = 0, A = 255 } },
|
||||
new object[] { "#ff000000", new TiledColor { R = 0, G = 0, B = 0, A = 255 } },
|
||||
new object[] { "#fe000000", new TiledColor { R = 0, G = 0, B = 0, A = 254 } },
|
||||
new object[] { "#fe00ff00", new TiledColor { R = 0, G = 255, B = 0, A = 254 } },
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(ColorData))]
|
||||
public void ReadXml_WhenPresent_SetsOptionalAttributes(string color, TiledColor expectedColor)
|
||||
{
|
||||
// Arrange
|
||||
var xml =
|
||||
$"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map
|
||||
version="1.2"
|
||||
class="class"
|
||||
orientation="orthogonal"
|
||||
renderorder="right-down"
|
||||
compressionlevel="5"
|
||||
width="10"
|
||||
height="10"
|
||||
tilewidth="32"
|
||||
tileheight="32"
|
||||
hexsidelength="10"
|
||||
staggeraxis="y"
|
||||
staggerindex="odd"
|
||||
parallaxoriginx="0.5"
|
||||
parallaxoriginy="0.5"
|
||||
backgroundcolor="{color}"
|
||||
nextlayerid="1"
|
||||
nextobjectid="1"
|
||||
infinite="1"
|
||||
>
|
||||
</map>
|
||||
""";
|
||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
||||
|
||||
// Act
|
||||
var map = Map.LoadFromStream(xmlStream);
|
||||
|
||||
// Assert
|
||||
// Assert all required properties are set
|
||||
Assert.Equal("1.2", map.Version);
|
||||
Assert.Equal("class", map.Class);
|
||||
Assert.Equal(Orientation.Orthogonal, map.Orientation);
|
||||
Assert.Equal(RenderOrder.RightDown, map.RenderOrder);
|
||||
Assert.Equal(5, map.CompressionLevel);
|
||||
Assert.Equal(10u, map.Width);
|
||||
Assert.Equal(10u, map.Height);
|
||||
Assert.Equal(32u, map.TileWidth);
|
||||
Assert.Equal(32u, map.TileHeight);
|
||||
Assert.Equal(10u, map.HexSideLength);
|
||||
Assert.Equal(StaggerAxis.Y, map.StaggerAxis);
|
||||
Assert.Equal(StaggerIndex.Odd, map.StaggerIndex);
|
||||
Assert.Equal(0.5f, map.ParallaxOriginX);
|
||||
Assert.Equal(0.5f, map.ParallaxOriginY);
|
||||
Assert.Equal(expectedColor, map.BackgroundColor);
|
||||
Assert.Equal(1u, map.NextLayerId);
|
||||
Assert.Equal(1u, map.NextObjectId);
|
||||
Assert.True(map.Infinite);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadXml_Always_ReadsPropertiesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var xml =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map
|
||||
version="1.2"
|
||||
class="class"
|
||||
orientation="orthogonal"
|
||||
renderorder="right-down"
|
||||
compressionlevel="5"
|
||||
width="10"
|
||||
height="10"
|
||||
tilewidth="32"
|
||||
tileheight="32"
|
||||
parallaxoriginx="0.5"
|
||||
parallaxoriginy="0.5"
|
||||
nextlayerid="1"
|
||||
nextobjectid="1"
|
||||
infinite="1"
|
||||
>
|
||||
<properties>
|
||||
<property name="string" type="string" value="string"/>
|
||||
<property name="int" type="int" value="42"/>
|
||||
<property name="float" type="float" value="42.42"/>
|
||||
<property name="bool" type="bool" value="true"/>
|
||||
<property name="color" type="color" value="#ff0000"/>
|
||||
<property name="file" type="file" value="file"/>
|
||||
<property name="object" type="object" value="5"/>
|
||||
<property name="class" type="class" propertytype="TestClass">
|
||||
<properties>
|
||||
<property name="TestClassString" type="string" value="string"/>
|
||||
<property name="TestClassInt" type="int" value="43"/>
|
||||
</properties>
|
||||
</property>
|
||||
</properties>
|
||||
</map>
|
||||
""";
|
||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
||||
|
||||
// Act
|
||||
var map = Map.LoadFromStream(xmlStream);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(map.Properties);
|
||||
Assert.Equal(8, map.Properties.Count);
|
||||
|
||||
Assert.Equal(PropertyType.String, map.Properties["string"].Type);
|
||||
Assert.Equal("string", map.GetProperty<StringProperty>("string").Value);
|
||||
|
||||
Assert.Equal(PropertyType.Int, map.Properties["int"].Type);
|
||||
Assert.Equal(42, map.GetProperty<IntProperty>("int").Value);
|
||||
|
||||
Assert.Equal(PropertyType.Float, map.Properties["float"].Type);
|
||||
Assert.Equal(42.42f, map.GetProperty<FloatProperty>("float").Value);
|
||||
|
||||
Assert.Equal(PropertyType.Bool, map.Properties["bool"].Type);
|
||||
Assert.True(map.GetProperty<BooleanProperty>("bool").Value);
|
||||
|
||||
Assert.Equal(PropertyType.Color, map.Properties["color"].Type);
|
||||
Assert.Equal(new TiledColor { R = 255, G = 0, B = 0, A = 255 }, map.GetProperty<ColorProperty>("color").Value);
|
||||
|
||||
Assert.Equal(PropertyType.File, map.Properties["file"].Type);
|
||||
Assert.Equal("file", map.GetProperty<FileProperty>("file").Value);
|
||||
|
||||
Assert.Equal(PropertyType.Object, map.Properties["object"].Type);
|
||||
Assert.Equal(5, map.GetProperty<ObjectProperty>("object").Value);
|
||||
|
||||
Assert.Equal(PropertyType.Class, map.Properties["class"].Type);
|
||||
var classProperty = map.GetProperty<ClassProperty>("class");
|
||||
Assert.Equal("TestClass", classProperty.PropertyType);
|
||||
Assert.Equal(2, classProperty.Value.Count);
|
||||
Assert.Equal("string", classProperty.GetProperty<StringProperty>("TestClassString").Value);
|
||||
Assert.Equal(43, classProperty.GetProperty<IntProperty>("TestClassInt").Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadXml_Always_1()
|
||||
{
|
||||
// Arrange
|
||||
var xml =
|
||||
"""
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<map
|
||||
version="1.2"
|
||||
class="class"
|
||||
orientation="orthogonal"
|
||||
renderorder="right-down"
|
||||
compressionlevel="5"
|
||||
width="10"
|
||||
height="10"
|
||||
tilewidth="32"
|
||||
tileheight="32"
|
||||
parallaxoriginx="0.5"
|
||||
parallaxoriginy="0.5"
|
||||
nextlayerid="1"
|
||||
nextobjectid="1"
|
||||
infinite="1"
|
||||
>
|
||||
<properties>
|
||||
<property name="string" type="string" value="string"/>
|
||||
<property name="int" type="int" value="42"/>
|
||||
<property name="float" type="float" value="42.42"/>
|
||||
<property name="bool" type="bool" value="true"/>
|
||||
<property name="color" type="color" value="#ff0000"/>
|
||||
<property name="file" type="file" value="file"/>
|
||||
<property name="object" type="object" value="5"/>
|
||||
<property name="class" type="class" propertytype="TestClass">
|
||||
<properties>
|
||||
<property name="TestClassString" type="string" value="string"/>
|
||||
<property name="TestClassInt" type="int" value="43"/>
|
||||
</properties>
|
||||
</property>
|
||||
</properties>
|
||||
<tileset firstgid="1" source="textures/tiles.tsx"/>
|
||||
</map>
|
||||
""";
|
||||
var xmlStream = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
||||
|
||||
// Act
|
||||
var map = Map.LoadFromStream(xmlStream);
|
||||
}
|
||||
}
|
||||
|
28
DotTiled.sln
Normal file
28
DotTiled.sln
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled", "DotTiled\DotTiled.csproj", "{80A60DE7-D6AE-4CC7-825F-75308D83F36D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotTiled.Tests", "DotTiled.Tests\DotTiled.Tests.csproj", "{C1311A5A-5206-467C-B323-B131CA11FDB8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{80A60DE7-D6AE-4CC7-825F-75308D83F36D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{C1311A5A-5206-467C-B323-B131CA11FDB8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
9
DotTiled/DotTiled.csproj
Normal file
9
DotTiled/DotTiled.csproj
Normal file
|
@ -0,0 +1,9 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
809
DotTiled/Map.cs
Normal file
809
DotTiled/Map.cs
Normal file
|
@ -0,0 +1,809 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
using System.Xml.Schema;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace DotTiled;
|
||||
|
||||
public static class Helpers
|
||||
{
|
||||
public static void SetAtMostOnce<T>(ref T? field, T value, string fieldName)
|
||||
{
|
||||
if (field is not null)
|
||||
throw new XmlException($"{fieldName} already set");
|
||||
|
||||
field = value;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Orientation
|
||||
{
|
||||
[XmlEnum(Name = "orthogonal")]
|
||||
Orthogonal,
|
||||
|
||||
[XmlEnum(Name = "isometric")]
|
||||
Isometric,
|
||||
|
||||
[XmlEnum(Name = "staggered")]
|
||||
Staggered,
|
||||
|
||||
[XmlEnum(Name = "hexagonal")]
|
||||
Hexagonal
|
||||
}
|
||||
|
||||
public enum RenderOrder
|
||||
{
|
||||
[XmlEnum(Name = "right-down")]
|
||||
RightDown,
|
||||
|
||||
[XmlEnum(Name = "right-up")]
|
||||
RightUp,
|
||||
|
||||
[XmlEnum(Name = "left-down")]
|
||||
LeftDown,
|
||||
|
||||
[XmlEnum(Name = "left-up")]
|
||||
LeftUp
|
||||
}
|
||||
|
||||
public enum StaggerAxis
|
||||
{
|
||||
[XmlEnum(Name = "x")]
|
||||
X,
|
||||
|
||||
[XmlEnum(Name = "y")]
|
||||
Y
|
||||
}
|
||||
|
||||
public enum StaggerIndex
|
||||
{
|
||||
[XmlEnum(Name = "even")]
|
||||
Even,
|
||||
|
||||
[XmlEnum(Name = "odd")]
|
||||
Odd
|
||||
}
|
||||
|
||||
public class TiledColor : IParsable<TiledColor>, IEquatable<TiledColor>
|
||||
{
|
||||
public required byte R { get; set; }
|
||||
public required byte G { get; set; }
|
||||
public required byte B { get; set; }
|
||||
public byte A { get; set; } = 255;
|
||||
|
||||
public static TiledColor Parse(string s, IFormatProvider? provider)
|
||||
{
|
||||
TryParse(s, provider, out var result);
|
||||
return result ?? throw new FormatException($"Invalid format for TiledColor: {s}");
|
||||
}
|
||||
|
||||
public static bool TryParse(
|
||||
[NotNullWhen(true)] string? s,
|
||||
IFormatProvider? provider,
|
||||
[MaybeNullWhen(false)] out TiledColor result)
|
||||
{
|
||||
// Format: #RRGGBB or #AARRGGBB
|
||||
if (s is null || s.Length != 7 && s.Length != 9 || s[0] != '#')
|
||||
{
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.Length == 7)
|
||||
{
|
||||
result = new TiledColor
|
||||
{
|
||||
R = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),
|
||||
G = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),
|
||||
B = byte.Parse(s[5..7], NumberStyles.HexNumber, provider)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new TiledColor
|
||||
{
|
||||
A = byte.Parse(s[1..3], NumberStyles.HexNumber, provider),
|
||||
R = byte.Parse(s[3..5], NumberStyles.HexNumber, provider),
|
||||
G = byte.Parse(s[5..7], NumberStyles.HexNumber, provider),
|
||||
B = byte.Parse(s[7..9], NumberStyles.HexNumber, provider)
|
||||
};
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Equals(TiledColor? other)
|
||||
{
|
||||
if (other is null)
|
||||
return false;
|
||||
|
||||
return R == other.R && G == other.G && B == other.B && A == other.A;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj) => obj is TiledColor other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(R, G, B, A);
|
||||
}
|
||||
|
||||
public enum PropertyType
|
||||
{
|
||||
[XmlEnum(Name = "string")]
|
||||
String,
|
||||
|
||||
[XmlEnum(Name = "int")]
|
||||
Int,
|
||||
|
||||
[XmlEnum(Name = "float")]
|
||||
Float,
|
||||
|
||||
[XmlEnum(Name = "bool")]
|
||||
Bool,
|
||||
|
||||
[XmlEnum(Name = "color")]
|
||||
Color,
|
||||
|
||||
[XmlEnum(Name = "file")]
|
||||
File,
|
||||
|
||||
[XmlEnum(Name = "object")]
|
||||
Object,
|
||||
|
||||
[XmlEnum(Name = "class")]
|
||||
Class
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public interface IProperty : IXmlSerializable
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public PropertyType Type { get; set; }
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class BooleanProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required bool Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute<bool>("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class ColorProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required TiledColor Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute<TiledColor>("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class FileProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required string Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class FloatProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required float Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute<float>("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class IntProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required int Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute<int>("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class ObjectProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required int Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute<int>("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class StringProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required string Value { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
Value = reader.GetRequiredAttribute("value");
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "property")]
|
||||
public class ClassProperty : IProperty
|
||||
{
|
||||
public required string Name { get; set; }
|
||||
public required PropertyType Type { get; set; }
|
||||
public required string PropertyType { get; set; }
|
||||
public required Dictionary<string, IProperty> Value { get; set; }
|
||||
|
||||
public T GetProperty<T>(string propertyName) where T : IProperty =>
|
||||
(T)Value[propertyName];
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
PropertyType = reader.GetRequiredAttribute("propertytype");
|
||||
|
||||
// First read the start element
|
||||
reader.ReadStartElement("property");
|
||||
// Then read the properties
|
||||
Value = XmlHelpers.ReadProperties(reader);
|
||||
// Finally read the end element
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ObjectAlignment
|
||||
{
|
||||
[XmlEnum(Name = "unspecified")]
|
||||
Unspecified,
|
||||
|
||||
[XmlEnum(Name = "topleft")]
|
||||
TopLeft,
|
||||
|
||||
[XmlEnum(Name = "top")]
|
||||
Top,
|
||||
|
||||
[XmlEnum(Name = "topright")]
|
||||
TopRight,
|
||||
|
||||
[XmlEnum(Name = "left")]
|
||||
Left,
|
||||
|
||||
[XmlEnum(Name = "center")]
|
||||
Center,
|
||||
|
||||
[XmlEnum(Name = "right")]
|
||||
Right,
|
||||
|
||||
[XmlEnum(Name = "bottomleft")]
|
||||
BottomLeft,
|
||||
|
||||
[XmlEnum(Name = "bottom")]
|
||||
Bottom,
|
||||
|
||||
[XmlEnum(Name = "bottomright")]
|
||||
BottomRight
|
||||
}
|
||||
|
||||
public enum TileRenderSize
|
||||
{
|
||||
[XmlEnum(Name = "tile")]
|
||||
Tile,
|
||||
|
||||
[XmlEnum(Name = "grid")]
|
||||
Grid
|
||||
}
|
||||
|
||||
public enum FillMode
|
||||
{
|
||||
[XmlEnum(Name = "stretch")]
|
||||
Stretch,
|
||||
|
||||
[XmlEnum(Name = "preserve-aspect-fit")]
|
||||
PreserveAspectFit
|
||||
}
|
||||
|
||||
public enum ImageFormat
|
||||
{
|
||||
[XmlEnum(Name = "png")]
|
||||
Png,
|
||||
|
||||
[XmlEnum(Name = "gif")]
|
||||
Gif,
|
||||
|
||||
[XmlEnum(Name = "jpg")]
|
||||
Jpg,
|
||||
|
||||
[XmlEnum(Name = "bmp")]
|
||||
Bmp
|
||||
}
|
||||
|
||||
public enum TiledDataEncoding
|
||||
{
|
||||
[XmlEnum(Name = "csv")]
|
||||
Csv,
|
||||
|
||||
[XmlEnum(Name = "base64")]
|
||||
Base64
|
||||
}
|
||||
|
||||
public enum TiledDataCompression
|
||||
{
|
||||
[XmlEnum(Name = "gzip")]
|
||||
GZip,
|
||||
|
||||
[XmlEnum(Name = "zlib")]
|
||||
ZLib,
|
||||
|
||||
[XmlEnum(Name = "zstd")]
|
||||
ZStd
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "data")]
|
||||
public class TiledData : IXmlSerializable
|
||||
{
|
||||
public TiledDataEncoding? Encoding { get; set; }
|
||||
public TiledDataCompression? Compression { get; set; }
|
||||
public required int[] Data { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
ReadXmlAttributes(reader);
|
||||
ReadXmlElements(reader);
|
||||
}
|
||||
|
||||
private void ReadXmlAttributes(XmlReader reader)
|
||||
{
|
||||
Encoding = reader.GetOptionalAttributeEnum<TiledDataEncoding>("encoding");
|
||||
Compression = reader.GetOptionalAttributeEnum<TiledDataCompression>("compression");
|
||||
}
|
||||
|
||||
private void ReadXmlElements(XmlReader reader)
|
||||
{
|
||||
if (Encoding is null && Compression is null)
|
||||
{
|
||||
// Plain csv
|
||||
reader.ReadStartElement("data");
|
||||
var dataAsCsvStringFromFile = reader.ReadContentAsString();
|
||||
var data = dataAsCsvStringFromFile
|
||||
.Split((char[])['\n', '\r', ','], StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
Data = data;
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "image")]
|
||||
public class Image : IXmlSerializable
|
||||
{
|
||||
public ImageFormat? Format { get; set; }
|
||||
public string? ID { get; set; } = null; // Deprecated and unsupported
|
||||
public string? Source { get; set; }
|
||||
public TiledColor? TransparentColor { get; set; }
|
||||
public uint? Width { get; set; }
|
||||
public uint? Height { get; set; }
|
||||
|
||||
private TiledData? _data = null;
|
||||
public TiledData? Data
|
||||
{
|
||||
get => _data;
|
||||
set => _data = value;
|
||||
}
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
ReadXmlAttributes(reader);
|
||||
ReadXmlElements(reader);
|
||||
}
|
||||
|
||||
private void ReadXmlAttributes(XmlReader reader)
|
||||
{
|
||||
Format = reader.GetOptionalAttributeEnum<ImageFormat>("format");
|
||||
ID = reader.GetOptionalAttribute("id");
|
||||
Source = reader.GetOptionalAttribute("source");
|
||||
TransparentColor = reader.GetOptionalAttributeClass<TiledColor>("trans");
|
||||
Width = reader.GetOptionalAttribute<uint>("width");
|
||||
Height = reader.GetOptionalAttribute<uint>("height");
|
||||
}
|
||||
|
||||
private void ReadXmlElements(XmlReader reader)
|
||||
{
|
||||
reader.ReadStartElement("image");
|
||||
|
||||
while (reader.IsStartElement())
|
||||
{
|
||||
var name = reader.Name;
|
||||
Action action = name switch
|
||||
{
|
||||
"data" => () => Helpers.SetAtMostOnce(ref _data, reader.ReadElementAs<TiledData>(), "Data"),
|
||||
_ => reader.Skip
|
||||
};
|
||||
|
||||
action();
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BaseTileset : IXmlSerializable
|
||||
{
|
||||
public required string? FirstGID { get; set; } // Not set in tsx
|
||||
public required string? Source { get; set; } // Not set in tsx
|
||||
public required string Name { get; set; }
|
||||
public required string Class { get; set; }
|
||||
public required uint TileWidth { get; set; }
|
||||
public required uint TileHeight { get; set; }
|
||||
public required uint? Spacing { get; set; }
|
||||
public required uint? Margin { get; set; }
|
||||
public required uint TileCount { get; set; }
|
||||
public required uint Columns { get; set; }
|
||||
public required ObjectAlignment ObjectAlignment { get; set; }
|
||||
public required TileRenderSize TileRenderSize { get; set; }
|
||||
public required FillMode FillMode { get; set; }
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
ReadXmlAttributes(reader);
|
||||
ReadXmlElements(reader);
|
||||
}
|
||||
|
||||
private void ReadXmlAttributes(XmlReader reader)
|
||||
{
|
||||
FirstGID = reader.GetOptionalAttribute("firstgid");
|
||||
Source = reader.GetOptionalAttribute("source");
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Class = reader.GetOptionalAttribute("class") ?? ""; // default value
|
||||
TileWidth = reader.GetRequiredAttribute<uint>("tilewidth");
|
||||
TileHeight = reader.GetRequiredAttribute<uint>("tileheight");
|
||||
Spacing = reader.GetOptionalAttribute<uint>("spacing");
|
||||
Margin = reader.GetOptionalAttribute<uint>("margin");
|
||||
TileCount = reader.GetRequiredAttribute<uint>("tilecount");
|
||||
Columns = reader.GetRequiredAttribute<uint>("columns");
|
||||
ObjectAlignment = reader.GetOptionalAttributeEnum<ObjectAlignment>("objectalignment") ?? ObjectAlignment.Unspecified;
|
||||
TileRenderSize = reader.GetOptionalAttributeEnum<TileRenderSize>("tilerendersize") ?? TileRenderSize.Tile;
|
||||
FillMode = reader.GetOptionalAttributeEnum<FillMode>("fillmode") ?? FillMode.Stretch;
|
||||
}
|
||||
|
||||
protected abstract void ReadXmlElements(XmlReader reader);
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "tileset")]
|
||||
public class ImageTileset : BaseTileset
|
||||
{
|
||||
private Image? _image = null;
|
||||
public required Image Image
|
||||
{
|
||||
get => _image ?? throw new InvalidOperationException("Image not set"); // Should not be able to happen
|
||||
set => _image = value;
|
||||
}
|
||||
|
||||
protected override void ReadXmlElements(XmlReader reader)
|
||||
{
|
||||
// Different types of tilesets
|
||||
reader.ReadStartElement("tileset");
|
||||
|
||||
while (reader.IsStartElement())
|
||||
{
|
||||
var name = reader.Name;
|
||||
Action action = name switch
|
||||
{
|
||||
"image" => () => Helpers.SetAtMostOnce(ref _image, reader.ReadElementAs<Image>(), "Image"),
|
||||
"tileoffset" => reader.Skip,
|
||||
"tile" => reader.Skip,
|
||||
"terraintypes" => reader.Skip,
|
||||
"wangsets" => reader.Skip,
|
||||
_ => reader.Skip
|
||||
};
|
||||
|
||||
action();
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "layer")]
|
||||
public class Layer : IXmlSerializable
|
||||
{
|
||||
public required string ID { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string Class { get; set; }
|
||||
public required uint X { get; set; }
|
||||
public required uint Y { get; set; }
|
||||
public required uint Width { get; set; }
|
||||
public required uint Height { get; set; }
|
||||
public required float Opacity { get; set; }
|
||||
public required bool Visible { get; set; }
|
||||
public required TiledColor? TintColor { get; set; }
|
||||
public required float OffsetX { get; set; }
|
||||
public required float OffsetY { get; set; }
|
||||
public required float ParallaxX { get; set; }
|
||||
public required float ParallaxY { get; set; }
|
||||
|
||||
private Dictionary<string, IProperty>? _properties = null;
|
||||
public required Dictionary<string, IProperty>? Properties
|
||||
{
|
||||
get => _properties;
|
||||
set => _properties = value;
|
||||
}
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
ReadXmlAttributes(reader);
|
||||
ReadXmlElements(reader);
|
||||
}
|
||||
|
||||
private void ReadXmlAttributes(XmlReader reader)
|
||||
{
|
||||
ID = reader.GetRequiredAttribute("id");
|
||||
Name = reader.GetRequiredAttribute("name");
|
||||
Class = reader.GetOptionalAttribute("class") ?? ""; // default value
|
||||
X = reader.GetRequiredAttribute<uint>("x");
|
||||
Y = reader.GetRequiredAttribute<uint>("y");
|
||||
Width = reader.GetRequiredAttribute<uint>("width");
|
||||
Height = reader.GetRequiredAttribute<uint>("height");
|
||||
Opacity = reader.GetRequiredAttribute<float>("opacity");
|
||||
Visible = reader.GetRequiredAttribute<uint>("visible") == 1;
|
||||
TintColor = reader.GetOptionalAttributeClass<TiledColor>("tintcolor");
|
||||
OffsetX = reader.GetRequiredAttribute<float>("offsetx");
|
||||
OffsetY = reader.GetRequiredAttribute<float>("offsety");
|
||||
ParallaxX = reader.GetRequiredAttribute<float>("parallaxx");
|
||||
ParallaxY = reader.GetRequiredAttribute<float>("parallaxy");
|
||||
}
|
||||
|
||||
private void ReadXmlElements(XmlReader reader)
|
||||
{
|
||||
reader.ReadStartElement("layer");
|
||||
|
||||
while (reader.IsStartElement())
|
||||
{
|
||||
var name = reader.Name;
|
||||
Action action = name switch
|
||||
{
|
||||
"properties" => () => Helpers.SetAtMostOnce(ref _properties, XmlHelpers.ReadProperties(reader), "Properties"),
|
||||
"data" => reader.Skip,
|
||||
_ => reader.Skip
|
||||
};
|
||||
|
||||
action();
|
||||
}
|
||||
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
[XmlRoot(ElementName = "map")]
|
||||
public class Map : IXmlSerializable
|
||||
{
|
||||
public required string Version { get; set; }
|
||||
public string? TiledVersion { get; set; }
|
||||
public required string Class { get; set; }
|
||||
public required Orientation Orientation { get; set; }
|
||||
public required RenderOrder RenderOrder { get; set; }
|
||||
public required int CompressionLevel { get; set; }
|
||||
public required uint Width { get; set; }
|
||||
public required uint Height { get; set; }
|
||||
public required uint TileWidth { get; set; }
|
||||
public required uint TileHeight { get; set; }
|
||||
public uint? HexSideLength { get; set; }
|
||||
public StaggerAxis? StaggerAxis { get; set; }
|
||||
public StaggerIndex? StaggerIndex { get; set; }
|
||||
public required float ParallaxOriginX { get; set; }
|
||||
public required float ParallaxOriginY { get; set; }
|
||||
public TiledColor? BackgroundColor { get; set; }
|
||||
public required uint NextLayerId { get; set; }
|
||||
public required uint NextObjectId { get; set; }
|
||||
public required bool Infinite { get; set; }
|
||||
|
||||
private Dictionary<string, IProperty>? _properties = null;
|
||||
public required Dictionary<string, IProperty>? Properties
|
||||
{
|
||||
get => _properties;
|
||||
set => _properties = value;
|
||||
}
|
||||
|
||||
public required List<BaseTileset> Tilesets { get; set; } = [];
|
||||
|
||||
public T GetProperty<T>(string propertyName) where T : IProperty
|
||||
{
|
||||
if (Properties is null)
|
||||
throw new InvalidOperationException("Properties not set");
|
||||
|
||||
return (T)Properties[propertyName];
|
||||
}
|
||||
|
||||
public static Map LoadFromStream(Stream stream)
|
||||
{
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
var serializer = new XmlSerializer(typeof(Map));
|
||||
return (Map)serializer.Deserialize(reader)!;
|
||||
}
|
||||
|
||||
public XmlSchema? GetSchema() => null;
|
||||
|
||||
public void ReadXml(XmlReader reader)
|
||||
{
|
||||
ReadXmlAttributes(reader);
|
||||
ReadXmlElements(reader, (s) => null);
|
||||
}
|
||||
|
||||
private void ReadXmlAttributes(XmlReader reader)
|
||||
{
|
||||
Version = reader.GetRequiredAttribute("version");
|
||||
TiledVersion = reader.GetOptionalAttribute("tiledversion");
|
||||
Class = reader.GetOptionalAttribute("class") ?? ""; // default value
|
||||
Orientation = reader.GetRequiredAttributeEnum<Orientation>("orientation");
|
||||
RenderOrder = reader.GetRequiredAttributeEnum<RenderOrder>("renderorder");
|
||||
CompressionLevel = reader.GetRequiredAttribute<int>("compressionlevel");
|
||||
Width = reader.GetRequiredAttribute<uint>("width");
|
||||
Height = reader.GetRequiredAttribute<uint>("height");
|
||||
TileWidth = reader.GetRequiredAttribute<uint>("tilewidth");
|
||||
TileHeight = reader.GetRequiredAttribute<uint>("tileheight");
|
||||
HexSideLength = reader.GetOptionalAttribute<uint>("hexsidelength");
|
||||
StaggerAxis = reader.GetOptionalAttributeEnum<StaggerAxis>("staggeraxis");
|
||||
StaggerIndex = reader.GetOptionalAttributeEnum<StaggerIndex>("staggerindex");
|
||||
ParallaxOriginX = reader.GetRequiredAttribute<float>("parallaxoriginx");
|
||||
ParallaxOriginY = reader.GetRequiredAttribute<float>("parallaxoriginy");
|
||||
BackgroundColor = reader.GetOptionalAttributeClass<TiledColor>("backgroundcolor");
|
||||
NextLayerId = reader.GetRequiredAttribute<uint>("nextlayerid");
|
||||
NextObjectId = reader.GetRequiredAttribute<uint>("nextobjectid");
|
||||
Infinite = reader.GetRequiredAttribute<uint>("infinite") == 1;
|
||||
}
|
||||
|
||||
private void ReadXmlElements(XmlReader reader, Func<string, BaseTileset> tilesetResolver)
|
||||
{
|
||||
reader.ReadStartElement("map");
|
||||
|
||||
while (reader.IsStartElement())
|
||||
{
|
||||
var name = reader.Name;
|
||||
Action action = name switch
|
||||
{
|
||||
"properties" => () => Helpers.SetAtMostOnce(ref _properties, XmlHelpers.ReadProperties(reader), "Properties"),
|
||||
"editorsettings" => reader.Skip,
|
||||
"tileset" => () => Tilesets.Add(XmlHelpers.ReadTileset(reader, tilesetResolver)),
|
||||
_ => reader.Skip
|
||||
};
|
||||
|
||||
action();
|
||||
}
|
||||
|
||||
reader.ReadEndElement();
|
||||
}
|
||||
|
||||
public void WriteXml(XmlWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
109
DotTiled/XML/ExtensionsXmlReader.cs
Normal file
109
DotTiled/XML/ExtensionsXmlReader.cs
Normal file
|
@ -0,0 +1,109 @@
|
|||
using System.Globalization;
|
||||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace DotTiled;
|
||||
|
||||
internal static class ExtensionsXmlReader
|
||||
{
|
||||
internal static string GetRequiredAttribute(this XmlReader reader, string attribute)
|
||||
{
|
||||
return reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required"); ;
|
||||
}
|
||||
|
||||
internal static T GetRequiredAttribute<T>(this XmlReader reader, string attribute) where T : IParsable<T>
|
||||
{
|
||||
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
||||
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static T GetRequiredAttributeEnum<T>(this XmlReader reader, string attribute) where T : Enum
|
||||
{
|
||||
var value = reader.GetAttribute(attribute) ?? throw new XmlException($"{attribute} attribute is required");
|
||||
return ParseEnumUsingXmlEnumAttribute<T>(value);
|
||||
}
|
||||
|
||||
internal static string? GetOptionalAttribute(this XmlReader reader, string attribute, string? defaultValue = default)
|
||||
{
|
||||
return reader.GetAttribute(attribute) ?? defaultValue;
|
||||
}
|
||||
|
||||
internal static T? GetOptionalAttribute<T>(this XmlReader reader, string attribute) where T : struct, IParsable<T>
|
||||
{
|
||||
var value = reader.GetAttribute(attribute);
|
||||
if (value is null)
|
||||
return null;
|
||||
|
||||
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static T? GetOptionalAttributeClass<T>(this XmlReader reader, string attribute) where T : class, IParsable<T>
|
||||
{
|
||||
var value = reader.GetAttribute(attribute);
|
||||
if (value is null)
|
||||
return null;
|
||||
|
||||
return T.Parse(value, CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
internal static T? GetOptionalAttributeEnum<T>(this XmlReader reader, string attribute) where T : struct, Enum
|
||||
{
|
||||
var value = reader.GetAttribute(attribute);
|
||||
return value != null ? ParseEnumUsingXmlEnumAttribute<T>(value) : null;
|
||||
}
|
||||
|
||||
internal static T ParseEnumUsingXmlEnumAttribute<T>(string value) where T : Enum
|
||||
{
|
||||
var enumType = typeof(T);
|
||||
var enumValues = Enum.GetValues(enumType);
|
||||
foreach (var enumValue in enumValues)
|
||||
{
|
||||
var enumMember = enumType.GetMember(enumValue.ToString()!)[0];
|
||||
var xmlEnumAttribute = enumMember.GetCustomAttributes(typeof(XmlEnumAttribute), false).FirstOrDefault() as XmlEnumAttribute;
|
||||
if (xmlEnumAttribute?.Name == value)
|
||||
return (T)enumValue;
|
||||
}
|
||||
|
||||
throw new XmlException($"Failed to parse enum value {value}");
|
||||
}
|
||||
|
||||
internal static List<T> ReadList<T>(this XmlReader reader, string wrapper, string elementName, Func<XmlReader, T> readElement)
|
||||
{
|
||||
var list = new List<T>();
|
||||
|
||||
if (reader.IsEmptyElement)
|
||||
return list;
|
||||
|
||||
reader.ReadStartElement(wrapper);
|
||||
while (reader.IsStartElement(elementName))
|
||||
{
|
||||
list.Add(readElement(reader));
|
||||
|
||||
if (reader.NodeType == XmlNodeType.EndElement)
|
||||
continue; // At end of list, no need to read again
|
||||
|
||||
reader.Read();
|
||||
}
|
||||
reader.ReadEndElement();
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public static T ReadElementAs<T>(this XmlReader reader) where T : IXmlSerializable
|
||||
{
|
||||
var serializer = new XmlSerializer(typeof(T));
|
||||
return (T)serializer.Deserialize(reader)!;
|
||||
}
|
||||
|
||||
public static int CountDirectChildrenWithName(this XmlReader reader, string name)
|
||||
{
|
||||
var subTree = reader.ReadSubtree();
|
||||
int count = 0;
|
||||
while (subTree.Read())
|
||||
{
|
||||
if (subTree.NodeType == XmlNodeType.Element && subTree.Name == name)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
52
DotTiled/XML/XmlHelpers.cs
Normal file
52
DotTiled/XML/XmlHelpers.cs
Normal file
|
@ -0,0 +1,52 @@
|
|||
using System.Xml;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace DotTiled;
|
||||
|
||||
public static class XmlHelpers
|
||||
{
|
||||
public static Dictionary<string, IProperty> ReadProperties(XmlReader reader)
|
||||
{
|
||||
return reader.ReadList<(string PropName, IProperty Prop)>("properties", "property",
|
||||
reader =>
|
||||
{
|
||||
var type = reader.GetRequiredAttributeEnum<PropertyType>("type");
|
||||
var propertyRuntimeType = type switch
|
||||
{
|
||||
PropertyType.String => typeof(StringProperty),
|
||||
PropertyType.Int => typeof(IntProperty),
|
||||
PropertyType.Float => typeof(FloatProperty),
|
||||
PropertyType.Bool => typeof(BooleanProperty),
|
||||
PropertyType.Color => typeof(ColorProperty),
|
||||
PropertyType.File => typeof(FileProperty),
|
||||
PropertyType.Object => typeof(ObjectProperty),
|
||||
PropertyType.Class => typeof(ClassProperty),
|
||||
_ => throw new XmlException("Invalid property type")
|
||||
};
|
||||
|
||||
var serializer = new XmlSerializer(propertyRuntimeType);
|
||||
var deserializedProperty = (IProperty)serializer.Deserialize(reader)!;
|
||||
return (deserializedProperty.Name, deserializedProperty);
|
||||
}
|
||||
).ToDictionary(x => x.PropName, x => x.Prop);
|
||||
}
|
||||
|
||||
public static BaseTileset ReadTileset(XmlReader reader, Func<string, BaseTileset> tilesetResolver)
|
||||
{
|
||||
var imageChildren = reader.CountDirectChildrenWithName("image");
|
||||
var tileChildren = reader.CountDirectChildrenWithName("tile");
|
||||
if (imageChildren == 0 && tileChildren == 0)
|
||||
{
|
||||
// This is a tileset that must have "source" set
|
||||
var source = reader.GetRequiredAttribute("source");
|
||||
return tilesetResolver(source);
|
||||
}
|
||||
if (imageChildren == 1)
|
||||
{
|
||||
// This is a single image tileset
|
||||
return reader.ReadElementAs<ImageTileset>();
|
||||
}
|
||||
|
||||
throw new XmlException("Invalid tileset");
|
||||
}
|
||||
}
|
0
Makefile
Normal file
0
Makefile
Normal file
Loading…
Add table
Reference in a new issue