diff --git a/SabreTools.Models/SeparatedValue/MetadataFile.cs b/SabreTools.Models/SeparatedValue/MetadataFile.cs new file mode 100644 index 00000000..cc9f81bd --- /dev/null +++ b/SabreTools.Models/SeparatedValue/MetadataFile.cs @@ -0,0 +1,9 @@ +namespace SabreTools.Models.SeparatedValue +{ + public class MetadataFile + { + public string[] Header { get; set; } + + public Row[]? Row { get; set; } + } +} \ No newline at end of file diff --git a/SabreTools.Models/SeparatedValue/Row.cs b/SabreTools.Models/SeparatedValue/Row.cs new file mode 100644 index 00000000..ebce2bb6 --- /dev/null +++ b/SabreTools.Models/SeparatedValue/Row.cs @@ -0,0 +1,66 @@ +namespace SabreTools.Models.SeparatedValue +{ + /// + /// Standardized variant of a row + /// + public class Row + { + /// File Name + public string FileName { get; set; } + + /// Internal Name + public string InternalName { get; set; } + + /// Description + public string Description { get; set; } + + /// Game Name + public string GameName { get; set; } + + /// Game Description + public string GameDescription { get; set; } + + /// Type + public string Type { get; set; } + + /// Rom Name + public string RomName { get; set; } + + /// Disk Name + public string DiskName { get; set; } + + /// Size, Numeric + public string Size { get; set; } + + /// CRC + public string CRC { get; set; } + + /// MD5 + public string MD5 { get; set; } + + /// SHA1 + public string SHA1 { get; set; } + + /// SHA256 + public string SHA256 { get; set; } + + /// SHA384, Optional + public string SHA384 { get; set; } + + /// SHA512, Optional + public string SHA512 { get; set; } + + /// SpamSum, Optional + public string SpamSum { get; set; } + + /// Nodump + public string Nodump { get; set; } + + #region DO NOT USE IN PRODUCTION + + /// Should be empty + public string[]? ADDITIONAL_ELEMENTS { get; set; } + + #endregion + } +} \ No newline at end of file diff --git a/SabreTools.Serialization/AttractMode.cs b/SabreTools.Serialization/AttractMode.cs index 24743cf6..1636552c 100644 --- a/SabreTools.Serialization/AttractMode.cs +++ b/SabreTools.Serialization/AttractMode.cs @@ -73,7 +73,7 @@ namespace SabreTools.Serialization break; // Parse the line into a row - Row row = null; + Row? row = null; if (reader.Line.Count < HeaderWithRomnameCount) { row = new Row diff --git a/SabreTools.Serialization/SeparatedValue.cs b/SabreTools.Serialization/SeparatedValue.cs new file mode 100644 index 00000000..afaedbae --- /dev/null +++ b/SabreTools.Serialization/SeparatedValue.cs @@ -0,0 +1,143 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using SabreTools.IO.Readers; +using SabreTools.Models.SeparatedValue; + +namespace SabreTools.Serialization +{ + /// + /// Serializer for separated-value variants + /// + public class SeparatedValue + { + private const int HeaderWithoutExtendedHashesCount = 14; + + private const int HeaderWithExtendedHashesCount = 17; + + /// + /// Deserializes a separated-value variant to the defined type + /// + /// Path to the file to deserialize + /// Character delimiter between values + /// Deserialized data on success, null on failure + public static MetadataFile? Deserialize(string path, char delim) + { + try + { + using var stream = PathProcessor.OpenStream(path); + return Deserialize(stream, delim); + } + catch + { + // TODO: Handle logging the exception + return default; + } + } + + /// + /// Deserializes a separated-value variant in a stream to the defined type + /// + /// Stream to deserialize + /// Character delimiter between values + /// Deserialized data on success, null on failure + public static MetadataFile? Deserialize(Stream? stream, char delim) + { + try + { + // If the stream is null + if (stream == null) + return default; + + // Setup the reader and output + var reader = new SeparatedValueReader(stream, Encoding.UTF8) + { + Header = true, + Separator = delim, + VerifyFieldCount = false, + }; + var dat = new MetadataFile(); + + // Read the header values first + if (!reader.ReadHeader()) + return null; + + dat.Header = reader.HeaderValues.ToArray(); + + // Loop through the rows and parse out values + var rows = new List(); + while (!reader.EndOfStream) + { + // If we have no next line + if (!reader.ReadNextLine()) + break; + + // Parse the line into a row + Row? row = null; + if (reader.Line.Count < HeaderWithExtendedHashesCount) + { + row = new Row + { + FileName = reader.Line[0], + InternalName = reader.Line[1], + Description = reader.Line[2], + GameName = reader.Line[3], + GameDescription = reader.Line[4], + Type = reader.Line[5], + RomName = reader.Line[6], + DiskName = reader.Line[7], + Size = reader.Line[8], + CRC = reader.Line[9], + MD5 = reader.Line[10], + SHA1 = reader.Line[11], + SHA256 = reader.Line[12], + Nodump = reader.Line[13], + }; + + // If we have additional fields + if (reader.Line.Count > HeaderWithoutExtendedHashesCount) + row.ADDITIONAL_ELEMENTS = reader.Line.Skip(HeaderWithoutExtendedHashesCount).ToArray(); + } + else + { + row = new Row + { + FileName = reader.Line[0], + InternalName = reader.Line[1], + Description = reader.Line[2], + GameName = reader.Line[3], + GameDescription = reader.Line[4], + Type = reader.Line[5], + RomName = reader.Line[6], + DiskName = reader.Line[7], + Size = reader.Line[8], + CRC = reader.Line[9], + MD5 = reader.Line[10], + SHA1 = reader.Line[11], + SHA256 = reader.Line[12], + SHA384 = reader.Line[13], + SHA512 = reader.Line[14], + SpamSum = reader.Line[15], + Nodump = reader.Line[16], + }; + + // If we have additional fields + if (reader.Line.Count > HeaderWithExtendedHashesCount) + row.ADDITIONAL_ELEMENTS = reader.Line.Skip(HeaderWithExtendedHashesCount).ToArray(); + } + rows.Add(row); + } + + // Assign the rows to the Dat and return + dat.Row = rows.ToArray(); + return dat; + } + catch + { + // TODO: Handle logging the exception + return default; + } + } + } +} \ No newline at end of file diff --git a/SabreTools.Test/Serialization/DeserializationTests.cs b/SabreTools.Test/Serialization/DeserializationTests.cs index a32042c9..ccc1ff5c 100644 --- a/SabreTools.Test/Serialization/DeserializationTests.cs +++ b/SabreTools.Test/Serialization/DeserializationTests.cs @@ -816,6 +816,32 @@ namespace SabreTools.Test.Parser } } + [Theory] + [InlineData("test-csv-files1.csv", ',', 2)] + [InlineData("test-csv-files2.csv", ',', 2)] + [InlineData("test-ssv-files1.ssv", ';', 2)] + [InlineData("test-ssv-files2.ssv", ';', 2)] + [InlineData("test-tsv-files2.tsv", '\t', 2)] + [InlineData("test-tsv-files1.tsv", '\t', 2)] + public void SeparatedValueDeserializeTest(string path, char delim, long count) + { + // Open the file for reading + string filename = System.IO.Path.Combine(Environment.CurrentDirectory, "TestData", path); + + // Deserialize the file + var dat = Serialization.SeparatedValue.Deserialize(filename, delim); + + // Validate the values + Assert.NotNull(dat); + Assert.Equal(count, dat.Row.Length); + + // Validate we're not missing any attributes or elements + foreach (var rom in dat.Row ?? Array.Empty()) + { + Assert.Null(rom.ADDITIONAL_ELEMENTS); + } + } + [Theory] [InlineData("test-softwarelist-files1.xml", 4531)] [InlineData("test-softwarelist-files2.xml", 2797)] diff --git a/SabreTools.Test/TestData/test-csv-files1.csv b/SabreTools.Test/TestData/test-csv-files1.csv new file mode 100644 index 00000000..a6208551 --- /dev/null +++ b/SabreTools.Test/TestData/test-csv-files1.csv @@ -0,0 +1,3 @@ +File Name,Internal Name,Description,Game Name,Game Description,Type,Rom Name,Disk Name,Size,CRC,MD5,SHA1,SHA256,Nodump +Original DatFile Name.xml,Original DatFile Name,Original DatFile,Game,A cool game,Rom,file.bin,,12345,0,d41d8cd98f00b204e9800998ecf8427e,da39a3ee5e6b4b0d3255bfef95601890afd80709,ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad,No +Original DatFile Name.xml,Original DatFile Name,Original DatFile,Game,A cool game,Disk,,file.chd,,,,da39a3ee5e6b4b0d3255bfef95601890afd80709,,No diff --git a/SabreTools.Test/TestData/test-csv-files2.csv b/SabreTools.Test/TestData/test-csv-files2.csv new file mode 100644 index 00000000..75d85260 --- /dev/null +++ b/SabreTools.Test/TestData/test-csv-files2.csv @@ -0,0 +1,3 @@ +File Name,Internal Name,Description,Game Name,Game Description,Type,Rom Name,Disk Name,Size,CRC,MD5,SHA1,SHA256,SHA384,SHA512,SpamSum,Nodump +Original DatFile Name.xml,Original DatFile Name,Original DatFile,Game,A cool game,Rom,file.bin,,12345,0,d41d8cd98f00b204e9800998ecf8427e,da39a3ee5e6b4b0d3255bfef95601890afd80709,ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad,cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7,ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f,QXX,No +Original DatFile Name.xml,Original DatFile Name,Original DatFile,Game,A cool game,Disk,,file.chd,,,,da39a3ee5e6b4b0d3255bfef95601890afd80709,,,,,No diff --git a/SabreTools.Test/TestData/test-ssv-files1.ssv b/SabreTools.Test/TestData/test-ssv-files1.ssv new file mode 100644 index 00000000..c7630cda --- /dev/null +++ b/SabreTools.Test/TestData/test-ssv-files1.ssv @@ -0,0 +1,3 @@ +File Name;Internal Name;Description;Game Name;Game Description;Type;Rom Name;Disk Name;Size;CRC;MD5;SHA1;SHA256;Nodump +Original DatFile Name.xml;Original DatFile Name;Original DatFile;Game;A cool game;Rom;file.bin;;12345;0;d41d8cd98f00b204e9800998ecf8427e;da39a3ee5e6b4b0d3255bfef95601890afd80709;ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad;No +Original DatFile Name.xml;Original DatFile Name;Original DatFile;Game;A cool game;Disk;;file.chd;;;;da39a3ee5e6b4b0d3255bfef95601890afd80709;;No diff --git a/SabreTools.Test/TestData/test-ssv-files2.ssv b/SabreTools.Test/TestData/test-ssv-files2.ssv new file mode 100644 index 00000000..66ab0f3d --- /dev/null +++ b/SabreTools.Test/TestData/test-ssv-files2.ssv @@ -0,0 +1,3 @@ +File Name;Internal Name;Description;Game Name;Game Description;Type;Rom Name;Disk Name;Size;CRC;MD5;SHA1;SHA256;SHA384;SHA512;SpamSum;Nodump +Original DatFile Name.xml;Original DatFile Name;Original DatFile;Game;A cool game;Rom;file.bin;;12345;0;d41d8cd98f00b204e9800998ecf8427e;da39a3ee5e6b4b0d3255bfef95601890afd80709;ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad;cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7;ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f;QXX;No +Original DatFile Name.xml;Original DatFile Name;Original DatFile;Game;A cool game;Disk;;file.chd;;;;da39a3ee5e6b4b0d3255bfef95601890afd80709;;;;;No diff --git a/SabreTools.Test/TestData/test-tsv-files1.tsv b/SabreTools.Test/TestData/test-tsv-files1.tsv new file mode 100644 index 00000000..6480dbbe --- /dev/null +++ b/SabreTools.Test/TestData/test-tsv-files1.tsv @@ -0,0 +1,3 @@ +File Name Internal Name Description Game Name Game Description Type Rom Name Disk Name Size CRC MD5 SHA1 SHA256 Nodump +Original DatFile Name.xml Original DatFile Name Original DatFile Game A cool game Rom file.bin 12345 0 d41d8cd98f00b204e9800998ecf8427e da39a3ee5e6b4b0d3255bfef95601890afd80709 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad No +Original DatFile Name.xml Original DatFile Name Original DatFile Game A cool game Disk file.chd da39a3ee5e6b4b0d3255bfef95601890afd80709 No diff --git a/SabreTools.Test/TestData/test-tsv-files2.tsv b/SabreTools.Test/TestData/test-tsv-files2.tsv new file mode 100644 index 00000000..2b0a9616 --- /dev/null +++ b/SabreTools.Test/TestData/test-tsv-files2.tsv @@ -0,0 +1,3 @@ +File Name Internal Name Description Game Name Game Description Type Rom Name Disk Name Size CRC MD5 SHA1 SHA256 SHA384 SHA512 SpamSum Nodump +Original DatFile Name.xml Original DatFile Name Original DatFile Game A cool game Rom file.bin 12345 0 d41d8cd98f00b204e9800998ecf8427e da39a3ee5e6b4b0d3255bfef95601890afd80709 ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7 ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f QXX No +Original DatFile Name.xml Original DatFile Name Original DatFile Game A cool game Disk file.chd da39a3ee5e6b4b0d3255bfef95601890afd80709 No