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