Not Rocket Science

A simple .ini File parser for C#

kay, so back in the old days, we had INI Files to store settings in. Those files are very simple:

[Section]
Key=Value
SomeOtherKey=SomeOtherValue

[AnotherSection]
Hello=World

Hardly exciting, but it gets the job done very well. Today I wanted to work with INI Files in my C# Application, both for reading and writing. But as I just found out, .net does not have built in functions for working with .ini files. It looks like they want everyone to work with XML Files now. Well, apart from the fact that XML looks like my keyboard puked on my hard drive and that the format is unneccesarily complicated for this purpose, I also found that reading and writing them is rather complicated, especially since the XmlSerializer has some stupid restrictions (one being that the class needs to be public - have they never heard of reflection?).

It's great that the .net Framework is so powerful - I believe I can write a function that downloads an MP3 off the internet and uses it to generate a gradient in an image which then gets encrypted and sent per e-Mail in maybe 5 lines or so, but when it comes to something simple as saving some Key/Value Pairs, I still need a crapton of code. So I binged the internets a bit and found some code that uses P/Invoke to call Kernel functions to handle ini files, but I want to avoid P/Invoke at all costs. So in the end I simply wrote a class to Load/Save Ini Files.

It's a simple class to interact with a simple file format that was created to store Key/Value pairs and that just works. Seriously, don't change what isn't broken. The class is licensed under WTFPL.

using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace StumDE.Misc
{
    /// <summary>
    /// Read/Write .ini Files
    /// 
    /// Version 1, 2009-08-15
    /// http://www.Stum.de
    /// </summary>
    /// <remarks>
    /// It supports the simple .INI Format:
    /// 
    /// [SectionName]
    /// Key1=Value1
    /// Key2=Value2
    /// 
    /// [Section2]
    /// Key3=Value3
    /// 
    /// You can have empty lines (they are ignored), but comments are not supported
    /// Key4=Value4 ; This is supposed to be a comment, but will be part of Value4
    /// 
    /// Whitespace is not trimmed from the beginning and end of either Key and Value
    /// 
    /// Licensed under WTFPL
    /// http://sam.zoy.org/wtfpl/
    /// </remarks>
    public class IniFile
    {
        private Dictionary<string, Dictionary<string, string>> _iniFileContent;
        private readonly Regex _sectionRegex = new Regex(@"(?<=\[)(?<SectionName>[^\]]+)(?=\])");
        private readonly Regex _keyValueRegex = new Regex(@"(?<Key>[^=]+)=(?<Value>.+)");

        public IniFile() : this(null){}

        public IniFile(string filename)
        {
            _iniFileContent = new Dictionary<string, Dictionary<string, string>>();
            if (filename != null) Load(filename);
        }

        /// <summary>
        /// Get a specific value from the .ini file
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="key"></param>
        /// <returns>The value of the given key in the given section, or NULL if not found</returns>
        public string GetValue(string sectionName, string key)
        {
            if (_iniFileContent.ContainsKey(sectionName) && _iniFileContent[sectionName].ContainsKey(key))
                return _iniFileContent[sectionName][key];
            else
                return null;
        }

        /// <summary>
        /// Set a specific value in a section
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public void SetValue(string sectionName, string key, string value)
        {
            if(!_iniFileContent.ContainsKey(sectionName)) _iniFileContent[sectionName] = new Dictionary<string, string>();
            _iniFileContent[sectionName][key] = value;
        }

        /// <summary>
        /// Get all the Values for a section
        /// </summary>
        /// <param name="sectionName"></param>
        /// <returns>A Dictionary with all the Key/Values for that section (maybe empty but never null)</returns>
        public Dictionary<string, string> GetSection(string sectionName)
        {
            if (_iniFileContent.ContainsKey(sectionName))
                return new Dictionary<string, string>(_iniFileContent[sectionName]);
            else
                return new Dictionary<string, string>();
        }

        /// <summary>
        /// Set an entire sections values
        /// </summary>
        /// <param name="sectionName"></param>
        /// <param name="sectionValues"></param>
        public void SetSection(string sectionName, IDictionary<string, string> sectionValues)
        {
            if (sectionValues == null) return;
            _iniFileContent[sectionName] = new Dictionary<string, string>(sectionValues);
        }


        /// <summary>
        /// Load an .INI File
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public bool Load(string filename)
        {
            if (File.Exists(filename))
            {
                try
                {
                    var content = File.ReadAllLines(filename);
                    _iniFileContent = new Dictionary<string, Dictionary<string, string>>();
                    string currentSectionName = string.Empty;
                    foreach (var line in content)
                    {
                        Match m = _sectionRegex.Match(line);
                        if (m.Success)
                        {
                            currentSectionName = m.Groups["SectionName"].Value;
                        }
                        else
                        {
                            m = _keyValueRegex.Match(line);
                            if (m.Success)
                            {
                                string key = m.Groups["Key"].Value;
                                string value = m.Groups["Value"].Value;

                                Dictionary<string, string> kvpList;
                                if (_iniFileContent.ContainsKey(currentSectionName))
                                {
                                    kvpList = _iniFileContent[currentSectionName];
                                }
                                else
                                {
                                    kvpList = new Dictionary<string, string>();
                                }
                                kvpList[key] = value;
                                _iniFileContent[currentSectionName] = kvpList;
                            }
                        }
                    }
                    return true;
                }
                catch
                {
                    return false;
                }

            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Save the content of this class to an INI File
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        public bool Save(string filename)
        {
            var sb = new StringBuilder();
            if (_iniFileContent != null)
            {
                foreach (var sectionName in _iniFileContent)
                {
                    sb.AppendFormat("[{0}]\r\n", sectionName.Key);
                    foreach (var keyValue in sectionName.Value)
                    {
                        sb.AppendFormat("{0}={1}\r\n", keyValue.Key, keyValue.Value);
                    }
                }
            }
            try
            {
                File.WriteAllText(filename, sb.ToString());
                return true;
            } catch
            {
                return false;
            }
        }
    }
}
Post Tags
My Software