using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace BinaryObjectScanner.Utilities
{
///
/// Key-value pair INI file
///
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;
}
// All other lines are ignored
}
}
}
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
}
}