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
///