Updated on 2021-12-13
An INI reader that is easy to use, easy to port, relatively efficient, and to the point
Occasionally, I need to read INI files, usually as one small part of a much larger project. As such, I really don't want it to require a bunch of infrastructure or other "buy in". I just want something simple that will get me what I need without much fuss. There is a lot of INI reader code out there, but a lot of it is gold plated, and so it wasn't what I needed. This is something simple and to the point you can just copy and paste into your project.
INI files are often laid out in sections, and under each section, there are key value pairs. This INI reader will combine multiple sections with the same name, and multiple keys under the same section as a single key with multiple values. It also allows you to specify values for a key one for each line. The default section is the empty string.
There are two methods of interest. The first one is for reading an INI file:
var ini = Ini.Read(myTextReader);
The caller is responsible for closing the reader. The ini variable now contains a type of IDictionary<string, IDictionary<string, object>>. That's kind of a hairy type but using it is pretty simple.
Each [section] in the INI file is a key in the outer dictionary. Each key = value pair becomes a dictionary entry under that section entry's inner dictionary. If there is only one value for a key, the dictionary value will be a string. If there is more than one value per key, that key's value will be an IList
As far as the other method, you can dump the results to a string using Ini.ToString(ini).
If you want to just paste it from here, here is the relevant code in its entirety:
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
static class Ini {
static void AddIniEntryValue(IDictionary<string, object> d, string line,
string name, int i, object o) {
if (o is string) {
var s = (string)o;
d.Remove(name);
var col = new List<string>();
d.Add(name, col);
col.Add(s);
col.Add(line.Substring(i + 1).TrimStart());
} else {
((List<string>)o).Add(line.Substring(i + 1).TrimStart());
}
}
/// <summary>
/// Reads an INI file as a nested dictionary.
/// The outer dictionary contains a dictionary for each section.
/// The inner dictionary contains a name, and a string or a list of strings
/// when an entry has multiple items.
/// </summary>
/// <param name="reader">The text reader</param>
/// <param name="comparer">The comparer to use for keys.
/// Defaults to culture invariant and case insensitive.</param>
/// <returns>A nested dictionary</returns>
public static IDictionary<string, IDictionary<string, object>>
Read(TextReader reader, IEqualityComparer<string> comparer = null) {
if (comparer == null) {
comparer = StringComparer.InvariantCultureIgnoreCase;
}
int lc = 1;
var result = new Dictionary<string, IDictionary<string, object>>(comparer);
string section = "";
string name = null;
string line;
while (null != (line = reader.ReadLine())) {
var i = line.IndexOf(';');
if (i > -1) {
line = line.Substring(0, i);
}
line = line.Trim();
if (!string.IsNullOrEmpty(line)) {
IDictionary<string, object> d;
if (line.Length > 2 && line[0] == '[' && line[line.Length - 1] == ']') {
section = line.Substring(1, line.Length - 2);
if (!result.TryGetValue(section, out d)) {
d = new Dictionary<string, object>(comparer);
result.Add(section, d);
}
} else {
d = result[section];
i = line.IndexOf('=');
if (i > -1) {
name = line.Substring(0, i).TrimEnd();
object o;
if (d.TryGetValue(name, out o)) {
AddIniEntryValue(d, line, name, i, o);
} else
d.Add(name, line.Substring(i + 1).TrimStart());
} else if (null == name) {
throw new IOException("Invalid INI file at line " + lc.ToString());
} else {
var o = d[name];
AddIniEntryValue(d, line, name, i, o);
}
}
}
++lc;
}
return result;
}
public static string ToString(IDictionary<string,IDictionary<string,object>> ini) {
var sb = new StringBuilder();
foreach (var sentry in ini) {
sb.AppendLine("[" + sentry.Key + "]");
var d = sentry.Value;
foreach (var entry in d) {
if (entry.Value is IList<string>) {
var l = ((IList<string>)entry.Value);
sb.AppendLine(string.Format("{0} = {1}", entry.Key, l[0]));
for (var i = 1; i < l.Count; ++i) {
sb.AppendLine("\t" + l[i]);
}
sb.AppendLine();
} else
sb.AppendLine(string.Format("{0} = {1}", entry.Key, entry.Value));
}
}
return sb.ToString();
}
}