diff --git a/SabreTools.Library/DatFiles/RomCenter.cs b/SabreTools.Library/DatFiles/RomCenter.cs index 445d4c49..e69b020a 100644 --- a/SabreTools.Library/DatFiles/RomCenter.cs +++ b/SabreTools.Library/DatFiles/RomCenter.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; using SabreTools.Library.Data; using SabreTools.Library.DatItems; +using SabreTools.Library.Readers; using SabreTools.Library.Tools; using NaturalSort; @@ -45,86 +45,325 @@ namespace SabreTools.Library.DatFiles bool clean, bool remUnicode) { - // Outsource the work of parsing the file to a helper - IniFile ini = new IniFile(filename); + // Prepare all intenral variables + Encoding enc = Utilities.GetEncoding(filename); + IniReader ir = Utilities.GetIniReader(filename, false); - // CREDITS section - Author = string.IsNullOrWhiteSpace(Author) ? ini["CREDITS.author"] : Author; - Version = string.IsNullOrWhiteSpace(Version) ? ini["CREDITS.version"] : Version; - Email = string.IsNullOrWhiteSpace(Email) ? ini["CREDITS.email"] : Email; - Homepage = string.IsNullOrWhiteSpace(Homepage) ? ini["CREDITS.homepage"] : Homepage; - Url = string.IsNullOrWhiteSpace(Url) ? ini["CREDITS.url"] : Url; - Date = string.IsNullOrWhiteSpace(Date) ? ini["CREDITS.date"] : Date; + // If we got a null reader, just return + if (ir == null) + return; - // DAT section - //RCVersion = string.IsNullOrWhiteSpace(RCVersion) ? ini["CREDITS.version"] : RCVersion; - //Plugin = string.IsNullOrWhiteSpace(Plugin) ? ini["CREDITS.plugin"] : Plugin; - if (ForceMerging == ForceMerging.None) + // Otherwise, read teh file to the end + try { - if (ini["DAT.split"] == "1") - ForceMerging = ForceMerging.Split; - else if (ini["DAT.merge"] == "1") - ForceMerging = ForceMerging.Merged; + ir.ReadNextLine(); + while (!ir.EndOfStream) + { + // We don't care about whitespace or comments + if (ir.RowType == IniRowType.None || ir.RowType == IniRowType.Comment) + { + ir.ReadNextLine(); + continue; + } + + // If we have a section + if (ir.RowType == IniRowType.SectionHeader) + { + switch (ir.Section.ToLowerInvariant()) + { + case "credits": + ReadCreditsSection(ir); + break; + + case "dat": + ReadDatSection(ir); + break; + + case "emulator": + ReadEmulatorSection(ir); + break; + + case "games": + ReadGamesSection(ir, sysid, srcid, clean, remUnicode); + break; + + // Unknown section so we ignore it + default: + ir.ReadNextLine(); + break; + } + } + } + } + catch (Exception ex) + { + Globals.Logger.Warning($"Exception found while parsing '{filename}': {ex}"); } - // EMULATOR section - Name = string.IsNullOrWhiteSpace(Name) ? ini["EMULATOR.refname"] : Name; - Description = string.IsNullOrWhiteSpace(Description) ? ini["EMULATOR.version"] : Description; + ir.Dispose(); + } - // GAMES section - foreach (string game in ini.Where(kvp => kvp.Value == null).Select(kvp => kvp.Key)) + /// + /// Read credits information + /// + /// IniReader to use to parse the credits + private void ReadCreditsSection(IniReader reader) + { + // If the reader is somehow null, skip it + if (reader == null) + return; + + reader.ReadNextLine(); + while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "credits") { - // Get the line into a separate variable so it can be manipulated - string line = game; - - // Remove INI prefixing - if (line.StartsWith("GAMES")) - line = line.Substring("GAMES.".Length); - - // If we have a valid game - if (line.StartsWith("¬")) + // We don't care about whitespace, comments, or invalid + if (reader.RowType != IniRowType.KeyValue) { - // Some old RC DATs have this behavior - if (line.Contains("¬N¬O")) - line = game.Replace("¬N¬O", string.Empty) + "¬¬"; - - /* - The rominfo order is as follows: - 1 - parent name - 2 - parent description - 3 - game name - 4 - game description - 5 - rom name - 6 - rom crc - 7 - rom size - 8 - romof name - 9 - merge name - */ - string[] rominfo = line.Split('¬'); - - // Try getting the size separately - if (!Int64.TryParse(rominfo[7], out long size)) - size = 0; - - Rom rom = new Rom - { - Name = rominfo[5], - Size = size, - CRC = Utilities.CleanHashData(rominfo[6], Constants.CRCLength), - ItemStatus = ItemStatus.None, - - MachineName = rominfo[3], - MachineDescription = rominfo[4], - CloneOf = rominfo[1], - RomOf = rominfo[8], - - SystemID = sysid, - SourceID = srcid, - }; - - // Now process and add the rom - ParseAddHelper(rom, clean, remUnicode); + reader.ReadNextLine(); + continue; } + + var kvp = reader.KeyValuePair; + + // If the KeyValuePair is invalid, skip it + if (kvp == null) + { + reader.ReadNextLine(); + continue; + } + + // Get all credits items (ONLY OVERWRITE IF THERE'S NO DATA) + switch (kvp?.Key.ToLowerInvariant()) + { + case "author": + Author = string.IsNullOrWhiteSpace(Author) ? kvp?.Value : Author; + reader.ReadNextLine(); + break; + + case "version": + Version = string.IsNullOrWhiteSpace(Version) ? kvp?.Value : Version; + reader.ReadNextLine(); + break; + + case "email": + Email = string.IsNullOrWhiteSpace(Email) ? kvp?.Value : Email; + reader.ReadNextLine(); + break; + + case "homepage": + Homepage = string.IsNullOrWhiteSpace(Homepage) ? kvp?.Value : Homepage; + reader.ReadNextLine(); + break; + + case "url": + Url = string.IsNullOrWhiteSpace(Url) ? kvp?.Value : Url; + reader.ReadNextLine(); + break; + + case "date": + Date = string.IsNullOrWhiteSpace(Date) ? kvp?.Value : Date; + reader.ReadNextLine(); + break; + + // Unknown value, just skip + default: + reader.ReadNextLine(); + break; + } + } + } + + /// + /// Read dat information + /// + /// IniReader to use to parse the credits + private void ReadDatSection(IniReader reader) + { + // If the reader is somehow null, skip it + if (reader == null) + return; + + reader.ReadNextLine(); + while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "dat") + { + // We don't care about whitespace, comments, or invalid + if (reader.RowType != IniRowType.KeyValue) + { + reader.ReadNextLine(); + continue; + } + + var kvp = reader.KeyValuePair; + + // If the KeyValuePair is invalid, skip it + if (kvp == null) + { + reader.ReadNextLine(); + continue; + } + + // Get all dat items (ONLY OVERWRITE IF THERE'S NO DATA) + switch (kvp?.Key.ToLowerInvariant()) + { + case "version": + string rcVersion = kvp?.Value; + reader.ReadNextLine(); + break; + + case "plugin": + string plugin = kvp?.Value; + reader.ReadNextLine(); + break; + + case "split": + if (ForceMerging == ForceMerging.None && kvp?.Value == "1") + ForceMerging = ForceMerging.Split; + + reader.ReadNextLine(); + break; + + case "merge": + if (ForceMerging == ForceMerging.None && kvp?.Value == "1") + ForceMerging = ForceMerging.Merged; + + reader.ReadNextLine(); + break; + + // Unknown value, just skip + default: + reader.ReadNextLine(); + break; + } + } + } + + /// + /// Read emulator information + /// + /// IniReader to use to parse the credits + private void ReadEmulatorSection(IniReader reader) + { + // If the reader is somehow null, skip it + if (reader == null) + return; + + reader.ReadNextLine(); + while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "emulator") + { + // We don't care about whitespace, comments, or invalid + if (reader.RowType != IniRowType.KeyValue) + { + reader.ReadNextLine(); + continue; + } + + var kvp = reader.KeyValuePair; + + // If the KeyValuePair is invalid, skip it + if (kvp == null) + { + reader.ReadNextLine(); + continue; + } + + // Get all emulator items (ONLY OVERWRITE IF THERE'S NO DATA) + switch (kvp?.Key.ToLowerInvariant()) + { + case "refname": + Name = string.IsNullOrWhiteSpace(Name) ? kvp?.Value : Name; + reader.ReadNextLine(); + break; + + case "version": + Description = string.IsNullOrWhiteSpace(Description) ? kvp?.Value : Description; + reader.ReadNextLine(); + break; + + // Unknown value, just skip + default: + reader.ReadNextLine(); + break; + } + } + } + + /// + /// Read games information + /// + /// IniReader to use to parse the credits + /// System ID for the DAT + /// Source ID for the DAT + /// True if game names are sanitized, false otherwise (default) + /// True if we should remove non-ASCII characters from output, false otherwise (default) + private void ReadGamesSection(IniReader reader, int sysid, int srcid, bool clean, bool remUnicode) + { + // If the reader is somehow null, skip it + if (reader == null) + return; + + reader.ReadNextLine(); + while (!reader.EndOfStream && reader.Section.ToLowerInvariant() == "games") + { + // We don't care about whitespace or comments + // We're keeping keyvalue in case a file has '=' in the row + if (reader.RowType != IniRowType.Invalid && reader.RowType != IniRowType.KeyValue) + { + reader.ReadNextLine(); + continue; + } + + // Roms are not valid row formats, usually + string line = reader.Line; + + // If we don't have a valid game, keep reading + if (!line.StartsWith("¬")) + { + reader.ReadNextLine(); + continue; + } + + // Some old RC DATs have this behavior + if (line.Contains("¬N¬O")) + line = line.Replace("¬N¬O", string.Empty) + "¬¬"; + + /* + The rominfo order is as follows: + 1 - parent name + 2 - parent description + 3 - game name + 4 - game description + 5 - rom name + 6 - rom crc + 7 - rom size + 8 - romof name + 9 - merge name + */ + string[] rominfo = line.Split('¬'); + + // Try getting the size separately + if (!Int64.TryParse(rominfo[7], out long size)) + size = 0; + + Rom rom = new Rom + { + Name = rominfo[5], + Size = size, + CRC = Utilities.CleanHashData(rominfo[6], Constants.CRCLength), + ItemStatus = ItemStatus.None, + + MachineName = rominfo[3], + MachineDescription = rominfo[4], + CloneOf = rominfo[1], + RomOf = rominfo[8], + + SystemID = sysid, + SourceID = srcid, + }; + + // Now process and add the rom + ParseAddHelper(rom, clean, remUnicode); + + reader.ReadNextLine(); } } @@ -264,49 +503,38 @@ namespace SabreTools.Library.DatFiles if (ignoreblanks && (datItem.ItemType == ItemType.Rom && ((datItem as Rom).Size == 0 || (datItem as Rom).Size == -1))) return true; + /* + The rominfo order is as follows: + 1 - parent name + 2 - parent description + 3 - game name + 4 - game description + 5 - rom name + 6 - rom crc + 7 - rom size + 8 - romof name + 9 - merge name + */ + try { // Pre-process the item name ProcessItemName(datItem, true); // Build the state based on excluded fields - switch (datItem.ItemType) - { - case ItemType.Disk: - sw.Write("¬"); - if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields))) - sw.Write(datItem.CloneOf); - sw.Write("¬"); - if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields))) - sw.Write(datItem.CloneOf); - sw.Write($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}"); - if (string.IsNullOrWhiteSpace(datItem.MachineDescription)) - sw.Write($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}"); - else - sw.Write($"¬{datItem.GetField(Field.Description, ExcludeFields)}"); - sw.Write($"¬{datItem.GetField(Field.Name, ExcludeFields)}"); - sw.Write("¬¬¬¬¬\n"); - break; - - case ItemType.Rom: - var rom = datItem as Rom; - sw.Write("¬"); - if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields))) - sw.Write(datItem.CloneOf); - sw.Write("¬"); - if (!string.IsNullOrWhiteSpace(datItem.GetField(Field.CloneOf, ExcludeFields))) - sw.Write(datItem.CloneOf); - sw.Write($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}"); - if (string.IsNullOrWhiteSpace(datItem.MachineDescription)) - sw.Write($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}"); - else - sw.Write($"¬{datItem.GetField(Field.Description, ExcludeFields)}"); - sw.Write($"¬{datItem.GetField(Field.Name, ExcludeFields)}"); - sw.Write($"¬{datItem.GetField(Field.CRC, ExcludeFields)}"); - sw.Write($"¬{datItem.GetField(Field.Size, ExcludeFields)}"); - sw.Write("¬¬¬\n"); - break; - } + sw.Write($"¬{datItem.GetField(Field.CloneOf, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.CloneOf, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}"); + if (string.IsNullOrWhiteSpace(datItem.MachineDescription)) + sw.Write($"¬{datItem.GetField(Field.MachineName, ExcludeFields)}"); + else + sw.Write($"¬{datItem.GetField(Field.Description, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.Name, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.CRC, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.Size, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.RomOf, ExcludeFields)}"); + sw.Write($"¬{datItem.GetField(Field.Merge, ExcludeFields)}"); + sw.Write("¬\n"); sw.Flush(); } diff --git a/SabreTools.Library/Data/Enums.cs b/SabreTools.Library/Data/Enums.cs index 677e503a..7e171970 100644 --- a/SabreTools.Library/Data/Enums.cs +++ b/SabreTools.Library/Data/Enums.cs @@ -438,6 +438,22 @@ #endregion + #region Reader related + + /// + /// Different types of INI rows being parsed + /// + public enum IniRowType + { + None, + SectionHeader, + KeyValue, + Comment, + Invalid, + } + + #endregion + #region Skippers and Mappers /// diff --git a/SabreTools.Library/Readers/IniReader.cs b/SabreTools.Library/Readers/IniReader.cs new file mode 100644 index 00000000..60419ec6 --- /dev/null +++ b/SabreTools.Library/Readers/IniReader.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +using SabreTools.Library.Data; + +namespace SabreTools.Library.Readers +{ + public class IniReader : IDisposable + { + /// + /// Internal stream reader for inputting + /// + private StreamReader sr; + + /// + /// Get if at end of stream + /// + public bool EndOfStream + { + get + { + return sr?.EndOfStream ?? true; + } + } + + /// + /// Contents of the currently read line as a key value pair + /// + public KeyValuePair? KeyValuePair { get; private set; } = null; + + /// + /// Contents of the currently read line + /// + public string Line { get; private set; } = string.Empty; + + /// + /// Current row type + /// + public IniRowType RowType { get; private set; } = IniRowType.None; + + /// + /// Current section being read + /// + public string Section { get; private set; } = string.Empty; + + /// + /// Validate that rows are in key=value format + /// + public bool ValidateRows { get; set; } = true; + + /// + /// Constructor for reading from a file + /// + public IniReader(string filename) + { + sr = new StreamReader(filename); + } + + /// + /// Constructor for reading from a stream + /// + public IniReader(Stream stream, Encoding encoding) + { + sr = new StreamReader(stream, encoding); + } + + /// + /// Read the next line in the INI file + /// + public bool ReadNextLine() + { + if (!(sr.BaseStream?.CanRead ?? false) || sr.EndOfStream) + return false; + + Line = sr.ReadLine().Trim(); + ProcessLine(); + return true; + } + + /// + /// Process the current line and extract out values + /// + private void ProcessLine() + { + // Comment + if (Line.StartsWith(";")) + { + KeyValuePair = null; + RowType = IniRowType.Comment; + } + + // Section + else if (Line.StartsWith("[") && Line.EndsWith("]")) + { + KeyValuePair = null; + RowType = IniRowType.SectionHeader; + Section = Line.TrimStart('[').TrimEnd(']'); + } + + // KeyValuePair + else if (Line.Contains("=")) + { + // Split the line by '=' for key-value pairs + string[] data = Line.Split('='); + + // If the value field contains an '=', we need to put them back in + string key = data[0].Trim(); + string value = string.Join("=", data.Skip(1)).Trim(); + + KeyValuePair = new KeyValuePair(key, value); + RowType = IniRowType.KeyValue; + } + + // Empty + else if (string.IsNullOrEmpty(Line)) + { + KeyValuePair = null; + Line = string.Empty; + RowType = IniRowType.None; + } + + // Invalid + else + { + KeyValuePair = null; + RowType = IniRowType.Invalid; + + if (ValidateRows) + throw new InvalidDataException($"Invalid INI row found, cannot continue: {Line}"); + } + } + + /// + /// Dispose of the reader + /// + public void Dispose() + { + sr.Dispose(); + } + } +} diff --git a/SabreTools.Library/Tools/IniFile.cs b/SabreTools.Library/Tools/IniFile.cs deleted file mode 100644 index 73e2f116..00000000 --- a/SabreTools.Library/Tools/IniFile.cs +++ /dev/null @@ -1,305 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace SabreTools.Library.Tools -{ - public class IniFile : IDictionary - { - private Dictionary _keyValuePairs = new Dictionary(); - - public string this[string key] - { - get - { - if (_keyValuePairs == null) - _keyValuePairs = new Dictionary(); - - key = key.ToLowerInvariant(); - if (_keyValuePairs.ContainsKey(key)) - return _keyValuePairs[key]; - - return null; - } - set - { - if (_keyValuePairs == null) - _keyValuePairs = new Dictionary(); - - key = key.ToLowerInvariant(); - _keyValuePairs[key] = value; - } - } - - /// - /// Create an empty INI file - /// - public IniFile() - { - } - - /// - /// Populate an INI file from path - /// - public IniFile(string path) - { - this.Parse(path); - } - - /// - /// Populate an INI file from stream - /// - public IniFile(Stream stream) - { - this.Parse(stream); - } - - /// - /// Add or update a key and value to the INI file - /// - public void AddOrUpdate(string key, string value) - { - _keyValuePairs[key.ToLowerInvariant()] = value; - } - - /// - /// Remove a key from the INI file - /// - public void Remove(string key) - { - _keyValuePairs.Remove(key.ToLowerInvariant()); - } - - /// - /// Read an INI file based on the path - /// - public bool Parse(string path) - { - // If we don't have a file, we can't read it - if (!File.Exists(path)) - return false; - - using (var fileStream = File.OpenRead(path)) - { - return Parse(fileStream); - } - } - - /// - /// Read an INI file from a stream - /// - public bool Parse(Stream stream) - { - // If the stream is invalid or unreadable, we can't process it - if (stream == null || !stream.CanRead || stream.Position >= stream.Length - 1) - return false; - - // Keys are case-insensitive by default - try - { - using (StreamReader sr = new StreamReader(stream)) - { - string section = string.Empty; - while (!sr.EndOfStream) - { - string line = sr.ReadLine().Trim(); - - // Comments start with ';' - if (line.StartsWith(";")) - { - // No-op, we don't process comments - } - - // Section titles are surrounded by square brackets - else if (line.StartsWith("[")) - { - section = line.TrimStart('[').TrimEnd(']'); - } - - // Valid INI lines are in the format key=value - else if (line.Contains("=")) - { - // Split the line by '=' for key-value pairs - string[] data = line.Split('='); - - // If the value field contains an '=', we need to put them back in - string key = data[0].Trim(); - string value = string.Join("=", data.Skip(1)).Trim(); - - // Section names are prepended to the key with a '.' separating - if (!string.IsNullOrEmpty(section)) - key = $"{section}.{key}"; - - // Set or overwrite keys in the returned dictionary - _keyValuePairs[key.ToLowerInvariant()] = value; - } - - // Lines that aren't a section or key=value are assumed to be key=null - else - { - // Section names are prepended to the key with a '.' separating - if (!string.IsNullOrEmpty(section)) - line = $"{section}.{line}"; - - // Note that these items are NOT normalized - _keyValuePairs[line] = null; - } - } - } - } - catch - { - // We don't care what the error was, just catch and return - return false; - } - - return true; - } - - /// - /// Write an INI file to a path - /// - public bool Write(string path) - { - // If we don't have a valid dictionary with values, we can't write out - if (_keyValuePairs == null || _keyValuePairs.Count == 0) - return false; - - using (var fileStream = File.OpenWrite(path)) - { - return Write(fileStream); - } - } - - /// - /// Write an INI file to a stream - /// - public bool Write(Stream stream) - { - // If we don't have a valid dictionary with values, we can't write out - if (_keyValuePairs == null || _keyValuePairs.Count == 0) - return false; - - // If the stream is invalid or unwritable, we can't output to it - if (stream == null || !stream.CanWrite || stream.Position >= stream.Length - 1) - return false; - - try - { - using (StreamWriter sw = new StreamWriter(stream)) - { - // Order the dictionary by keys to link sections together - var orderedKeyValuePairs = _keyValuePairs.OrderBy(kvp => kvp.Key); - - string section = string.Empty; - foreach (var keyValuePair in orderedKeyValuePairs) - { - // Extract the key and value - string key = keyValuePair.Key; - string value = keyValuePair.Value; - - // We assume '.' is a section name separator - if (key.Contains('.')) - { - // Split the key by '.' - string[] data = keyValuePair.Key.Split('.'); - - // If the key contains an '.', we need to put them back in - string newSection = data[0].Trim(); - key = string.Join(".", data.Skip(1)).Trim(); - - // If we have a new section, write it out - if (!string.Equals(newSection, section, StringComparison.OrdinalIgnoreCase)) - { - sw.WriteLine($"[{newSection}]"); - section = newSection; - } - } - - // Now write out the key and value in a standardized way - sw.WriteLine($"{key}={value}"); - } - } - } - catch - { - // We don't care what the error was, just catch and return - return false; - } - - return true; - } - - #region IDictionary Impelementations - - public ICollection Keys => ((IDictionary)_keyValuePairs).Keys; - - public ICollection Values => ((IDictionary)_keyValuePairs).Values; - - public int Count => ((ICollection>)_keyValuePairs).Count; - - public bool IsReadOnly => ((ICollection>)_keyValuePairs).IsReadOnly; - - public void Add(string key, string value) - { - ((IDictionary)_keyValuePairs).Add(key.ToLowerInvariant(), value); - } - - bool IDictionary.Remove(string key) - { - return ((IDictionary)_keyValuePairs).Remove(key.ToLowerInvariant()); - } - - public bool TryGetValue(string key, out string value) - { - return ((IDictionary)_keyValuePairs).TryGetValue(key.ToLowerInvariant(), out value); - } - - public void Add(KeyValuePair item) - { - var newItem = new KeyValuePair(item.Key.ToLowerInvariant(), item.Value); - ((ICollection>)_keyValuePairs).Add(newItem); - } - - public void Clear() - { - ((ICollection>)_keyValuePairs).Clear(); - } - - public bool Contains(KeyValuePair item) - { - var newItem = new KeyValuePair(item.Key.ToLowerInvariant(), item.Value); - return ((ICollection>)_keyValuePairs).Contains(newItem); - } - - public bool ContainsKey(string key) - { - return _keyValuePairs.ContainsKey(key.ToLowerInvariant()); - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - ((ICollection>)_keyValuePairs).CopyTo(array, arrayIndex); - } - - public bool Remove(KeyValuePair item) - { - var newItem = new KeyValuePair(item.Key.ToLowerInvariant(), item.Value); - return ((ICollection>)_keyValuePairs).Remove(newItem); - } - - public IEnumerator> GetEnumerator() - { - return ((IEnumerable>)_keyValuePairs).GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)_keyValuePairs).GetEnumerator(); - } - - #endregion - } -} diff --git a/SabreTools.Library/Tools/Utilities.cs b/SabreTools.Library/Tools/Utilities.cs index 0c516b43..3d239e2c 100644 --- a/SabreTools.Library/Tools/Utilities.cs +++ b/SabreTools.Library/Tools/Utilities.cs @@ -4,7 +4,6 @@ using System.IO; using System.Linq; using System.Net; using System.Reflection; -using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -15,6 +14,7 @@ using SabreTools.Library.Data; using SabreTools.Library.DatFiles; using SabreTools.Library.DatItems; using SabreTools.Library.FileTypes; +using SabreTools.Library.Readers; using SabreTools.Library.Reports; using SabreTools.Library.Skippers; using Compress.ThreadReaders; @@ -1737,6 +1737,28 @@ namespace SabreTools.Library.Tools return true; } + /// + /// Get the IniReader associated with a file, if possible + /// + /// Name of the file to be parsed + /// True if rows should be in a proper format, false if invalid is okay + /// The IniReader representing the (possibly converted) file, null otherwise + public static IniReader GetIniReader(string filename, bool validateRows) + { + Globals.Logger.Verbose($"Attempting to read file: {filename}"); + + // Check if file exists + if (!File.Exists(filename)) + { + Globals.Logger.Warning($"File '{filename}' could not read from!"); + return null; + } + + IniReader ir = new IniReader(filename); + ir.ValidateRows = validateRows; + return ir; + } + /// /// Retrieve a list of just files from inputs ///