Click here to Skip to main content
15,885,757 members
Articles / Programming Languages / C#

Ini Handler

Rate me:
Please Sign up or sign in to vote.
4.00/5 (24 votes)
2 Jun 20042 min read 101.9K   2K   53  
Permits simple access to ini files
/*	 /--==###################==--\
 *	 |	Bram's Ini File Handler  |
 *	 \--==###################==--/
 * 
 * This handles Ini files and all their content.
 * 
 * Some explanation:
 * Categories are in fact sections, but i didn't think
 * "sections" so i wrote "categories". Sorry.
 * 
 * comment lines in ini files begin with #, ; or //
 * multi-line are not supported (Because I've never seen such)
 * 
 * It ignores comments on reading but can write them
 * 
 * How it works:
 * All data is saved in one System.Collections.SortedList which
 * contains the category names as keys, and all key-value pairs
 * as values, saved as SortedList too:
 * 
 * explanation sheet
 * 
 * SortedList Categories
 * {
 *		{"Category1", {Key1, value1}
 *					  {Key2, value2}
 *					  ...
 *								   }
 *		{"Category2", {Key1, value1}
 *					  {Key2, value2}
 *					  ...
 *								   }
 *		...
 * }
 * 
 * that behaves like an array in an array (array[][]), but with dynamic bounds
 * and strings as indexers.
 * 
 * I hope you did understand this, it would have been easier to explain 
 * in French or German...
 * 
 * You can make with it what you want, but I would be pleased to
 * hear some feedback. (Ok, I admit: I would be pleased only if it's
 * positive feedback...)
 * 
 * Send me an email! kratchkov@inbox.lv
 * 
 * Thanks
 * 
 * DISCLAIMER:
 * THIS CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESSED OR IMPLIED INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.  THE  ENTIRE RISK  AS TO THE  QUALITY AND PERFORMANCE OF THIS
 * CODE IS WITH YOU. SHOULD THIS CODE PROVE DEFECTIVE, YOU ASSUME
 * THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION .
 * You take full responsibility for the use of this code and any
 * consequences thereof. I can not accept liability for damages
 * or failures arising from the use of this code, or parts of this code.
 */

using System;
using System.IO;
using System.Text;
using System.Collections;

namespace cs_IniHandlerDevelop
{
	/// <summary>
	/// Handles Ini categories, keys and their associated values, static methods implemented for file
	/// handling (saving and reading)
	/// </summary>
	public class IniStructure
	{
		#region Ini structure code
		private SortedList Categories = new SortedList();

		/// <summary>
		/// Initialies a new IniStructure
		/// </summary>
		public IniStructure()
		{
			return; // There's nothing to do...
		}

		/// <summary>
		/// Adds a category to the IniStructure
		/// </summary>
		/// <param name="Name">Name of the new category</param>
		public bool AddCategory(string Name)
		{
			if (Name == "" | Categories.ContainsKey(Name))
				return false;
			if (Name.IndexOf('=') != -1
				| Name.IndexOf('[') != -1
				| Name.IndexOf(']') != -1) // these characters are not allowed in a category name
				return false;

			Categories.Add(Name, new SortedList());
			return true;
		}

		/// <summary>
		/// Deletes a category and its contents
		/// </summary>
		/// <param name="Name">category to delete</param>
		public bool DeleteCategory(string Name)
		{
			if (Name == "" | !Categories.ContainsKey(Name))
				return false;
			Categories.Remove(Name);
			return true;
		}

		/// <summary>
		/// Renames a category
		/// </summary>
		/// <param name="Name">Category to rename</param>
		/// <param name="NewName">New name</param>
		public bool RenameCategory(string Name, string NewName)
		{ //		Or rather moves a category to a new name
			if (Name == "" | !Categories.ContainsKey(Name) | NewName == "")
				return false;

			if (NewName.IndexOf('=') != -1
				| NewName.IndexOf('[') != -1
				| NewName.IndexOf(']') != -1) // these characters are not allowed in a category name
				return false;

			SortedList Category = (SortedList)(Categories[Name]);
			Categories.Add(NewName, Category);
			this.DeleteCategory(Name);
			return true;
		}

		/// <summary>
		/// Returns the names of all categories
		/// </summary>
		/// <returns></returns>
		public string[] GetCategories()
		{
			string[] CatNames = new string[Categories.Count];
			IList KeyList = Categories.GetKeyList();
			int KeyCount = Categories.Count;
			for (int i = 0; i < KeyCount; i++)
			{
				CatNames[i] = KeyList[i].ToString();
			}
			return CatNames;
		}

		/// <summary>
		/// Returns the name of a category by specifying the index.
		/// Useful to enumerate through all categories.
		/// </summary>
		/// <param name="Index">The category index</param>
		/// <returns></returns>
		public string GetCategoryName(int Index)
		{
			if (Index < 0 | Index >= Categories.Count)
				return null;
			return Categories.GetKey(Index).ToString();
		}

		/// <summary>
		/// Adds a key-value pair to a specified category
		/// </summary>
		/// <param name="CategoryName">Name of the category</param>
		/// <param name="Key">New name of the key</param>
		/// <param name="Value">Associated value</param>
		public bool AddValue(string CategoryName, string Key, string Value)
		{
			if (CategoryName == "" | Key == "")
				return false;
			if (Key.IndexOf('=') != -1
				| Key.IndexOf('[') != -1
				| Key.IndexOf(']') != -1	// these chars are not allowed for keynames
				| Key.IndexOf(';') != -1
				| Key.IndexOf('#') != -1
				)
				return false;
			if (!Categories.ContainsKey(CategoryName))
				return false;
			SortedList Category = (SortedList)(Categories[CategoryName]);
			if (Category.ContainsKey(Key))
				return false;
			Category.Add(Key, Value);
			return true;
		}

		/// <summary>
		/// Returns the value of a key-value pair in a specified category by specifying the key
		/// </summary>
		/// <param name="CategoryName">Name of the category</param>
		/// <param name="Key">Name of the Key</param>
		/// <returns></returns>
		public string GetValue(string CategoryName, string Key)
		{
			if (CategoryName == "" | Key == "")
				return null;
			if (!Categories.ContainsKey(CategoryName))
				return null;
			SortedList Category = (SortedList)(Categories[CategoryName]);
			if (!Category.ContainsKey(Key))
				return null;
			return Category[Key].ToString();
		}

		/// <summary>
		/// Returns the key-value pair in a specified category by specifying the index
		/// </summary>
		/// <param name="CategoryName">Index of the category</param>
		/// <param name="Key">Index of the Key</param>
		/// <returns></returns>
		public string GetValue(int CatIndex, int KeyIndex)
		{
			if (CatIndex < 0 | KeyIndex < 0
				|CatIndex >= Categories.Count)
				return null;
			SortedList Category = (SortedList)(Categories.GetByIndex(CatIndex));
			if (KeyIndex >= Category.Count)
				return null;
			return Category.GetByIndex(KeyIndex).ToString();
		}

		/// <summary>
		/// Returns the name of the key in a key-value pair in a specified category by specifying the index
		/// </summary>
		/// <param name="CatIndex">Index of the category</param>
		/// <param name="KeyIndex">Index of the key</param>
		/// <returns></returns>
		public string GetKeyName(int CatIndex, int KeyIndex)
		{
			if (CatIndex < 0 | KeyIndex < 0
				|CatIndex >= Categories.Count)
				return null;
			SortedList Category = (SortedList)(Categories.GetByIndex(CatIndex));
			if (KeyIndex >= Category.Count)
				return null;
			return Category.GetKey(KeyIndex).ToString();
		}


		/// <summary>
		/// Deletes a key-value pair
		/// </summary>
		/// <param name="CategoryName">Name of the category</param>
		/// <param name="Key">Name of the Key</param>
		public bool DeleteValue(string CategoryName, string Key)
		{
			if (CategoryName == "" | Key == "")
				return false;
			if (!Categories.ContainsKey(CategoryName))
				return false;
			SortedList Category = (SortedList)(Categories[CategoryName]);
			if (!Category.ContainsKey(Key))
				return false;
			Category.Remove(Key);
			return true;
		}

		/// <summary>
		/// Renames the keyname in a key-value pair
		/// </summary>
		/// <param name="CategoryName">Name of the category</param>
		/// <param name="KeyName">Name of the Key</param>
		/// <param name="NewKeyName">New name of the Key</param>
		public bool RenameKey(string CategoryName, string KeyName, string NewKeyName)
		{
			if (CategoryName == "" | KeyName == "" | NewKeyName == "")
				return false;
			if (!Categories.ContainsKey(CategoryName))
				return false;
			if (NewKeyName.IndexOf('=') != -1
				| NewKeyName.IndexOf('[') != -1
				| NewKeyName.IndexOf(']') != -1	// these chars are not allowed for keynames
				| NewKeyName.IndexOf(';') != -1
				| NewKeyName.IndexOf('#') != -1
				)
				return false;
			SortedList Category = (SortedList)(Categories[CategoryName]);
			if ( !Category.ContainsKey(KeyName))
				return false;
			
			object value = Category[KeyName];
			Category.Remove(KeyName);
			Category.Add(NewKeyName, value);
			return true;
		}

		/// <summary>
		/// Modifies the value in a key-value pair
		/// </summary>
		/// <param name="CategoryName">Name of the category</param>
		/// <param name="KeyName">Name of the Key</param>
		/// <param name="NewValue">New name of the Key</param>
		public bool ModifyValue(string CategoryName, string KeyName, string NewValue)
		{
			if (CategoryName == "" | KeyName == "")
				return false;
			if (!Categories.ContainsKey(CategoryName))
				return false;
			SortedList Category = (SortedList)(Categories[CategoryName]);
			if ( !Category.ContainsKey(KeyName))
				return false;
			
			Category[KeyName] = NewValue;
			return true;
		}

		/// <summary>
		/// Returns all keys in a category
		/// </summary>
		/// <param name="CategoryName">Name of the category</param>
		/// <returns></returns>
		public string[] GetKeys(string CategoryName)
		{
			SortedList Category = (SortedList)(Categories[CategoryName]);
			if (Category == null)
				return null;
			int KeyCount = Category.Count;
			string[] KeyNames = new string[KeyCount];
			IList KeyList = Category.GetKeyList();
			for (int i = 0; i < KeyCount; i++)
			{
				KeyNames[i] = KeyList[i].ToString();
			}
			return KeyNames;
		}

		#endregion

		#region Ini writing code
		/// <summary>
		/// Writes an IniStructure to a file with a comment.
		/// </summary>
		/// <param name="IniData">The contents to write</param>
		/// <param name="Filename">The complete path and name of the file</param>
		/// <param name="comment">Comment to add</param>
		/// <returns></returns>
		public static bool WriteIni(IniStructure IniData, string Filename, string comment)
		{
			string DataToWrite = CreateData(IniData, BuildComment(comment));
			return WriteFile(Filename, DataToWrite);
		}

		/// <summary>
		/// Writes an IniStructure to a file without a comment.
		/// </summary>
		/// <param name="IniData">The contents to write</param>
		/// <param name="Filename">The complete path and name of the file</param>
		/// <returns></returns>
		public static bool WriteIni(IniStructure IniData, string Filename)
		{
			string DataToWrite = CreateData(IniData);
			return WriteFile(Filename, DataToWrite);
		}

		private static bool WriteFile(string Filename, string Data)
		{	// Writes a string to a file
			try
			{
				FileStream IniStream = new FileStream(Filename,FileMode.Create);
				if (!IniStream.CanWrite)
				{
					IniStream.Close();
					return false;
				}
				StreamWriter writer = new StreamWriter(IniStream);
				writer.Write(Data);
				writer.Flush();
				writer.Close();
				IniStream.Close();
				return true;
			}
			catch
			{
				return false;
			}
		}

		private static string BuildComment(string comment)
		{ // Adds a # at the beginning of each line
			if (comment == "")
				return "";
			string[] Lines = DivideToLines(comment);
			string temp = "";
			foreach (string line in Lines)
			{
				temp += "# " + line + "\r\n";
			}
			return temp;
		}

		private static string CreateData(IniStructure IniData)
		{
			return CreateData(IniData,"");
		}

		private static string CreateData(IniStructure IniData, string comment)
		{	//Iterates through all categories and keys and appends all data to Data
			int CategoryCount = IniData.GetCategories().Length;
			int[] KeyCountPerCategory = new int[CategoryCount];
			string Data = comment;
			string[] temp = new string[2]; // will contain key-value pair
			
			for (int i = 0; i < CategoryCount; i++) // Gets keycount per category
			{
				string CategoryName = IniData.GetCategories()[i];
				KeyCountPerCategory[i] = IniData.GetKeys(CategoryName).Length;
			}

			for (int catcounter = 0; catcounter < CategoryCount; catcounter++)
			{
				Data += "\r\n[" + IniData.GetCategoryName(catcounter) + "]\r\n"; 
				// writes [Category] to Data
				for (int keycounter = 0; keycounter < KeyCountPerCategory[catcounter]; keycounter++)
				{
					temp[0] = IniData.GetKeyName(catcounter, keycounter);
					temp[1] = IniData.GetValue(catcounter, keycounter);
					Data += temp[0] + "=" + temp[1] + "\r\n";
					// writes the key-value pair to Data
				}
			}
			return Data;
		}
		#endregion

		#region Ini reading code

		/// <summary>
		/// Reads an ini file and returns the content as an IniStructure. Returns null if an error occurred.
		/// </summary>
		/// <param name="Filename">The filename to read</param>
		/// <returns></returns>
		public static IniStructure ReadIni(string Filename)
		{
			string Data = ReadFile(Filename);
			if (Data == null)
				return null;

			IniStructure data = InterpretIni(Data);
			
			return data;
		}

		public static IniStructure InterpretIni(string Data)
		{
			IniStructure IniData = new IniStructure();
			string[] Lines = RemoveAndVerifyIni(DivideToLines(Data));
			// Divides the Data in lines, removes comments and empty lines
			// and verifies if the ini is not corrupted
			// Returns null if it is.
			if (Lines == null)
				return null;

			if (IsLineACategoryDef(Lines[0]) != LineType.Category)
			{
				return null;
				// Ini is faulty - does not begin with a categorydef
			}
			string CurrentCategory = "";
			foreach (string line in Lines)
			{
				switch (IsLineACategoryDef(line))
				{
					case LineType.Category:	// the line is a correct category definition
						string NewCat = line.Substring(1,line.Length - 2);
						IniData.AddCategory(NewCat); // adds the category to the IniData
						CurrentCategory = NewCat;
						break;
					case LineType.NotACategory: // the line is not a category definition
						string[] keyvalue = GetDataFromLine(line);
						IniData.AddValue(CurrentCategory, keyvalue[0], keyvalue[1]);
						// Adds the key-value to the current category
						break;
					case LineType.Faulty: // the line is faulty
						return null;
				}
			}
			return IniData;
		}

		private static string ReadFile(string filename)
		{		// Reads a file to a string.
			if (!File.Exists(filename))
				return null;
			StringBuilder IniData;
			try
			{
				FileStream IniStream = new FileStream(filename,FileMode.Open,FileAccess.Read);
				if (!IniStream.CanRead)
				{
					IniStream.Close();
					return null;
				}
				StreamReader reader = new StreamReader(IniStream);
				IniData = new StringBuilder();
				IniData.Append(reader.ReadToEnd());
				reader.Close();
				IniStream.Close();
				return IniData.ToString();
			}
			catch
			{
				return null;
			}
		}
		
		private static string[] GetDataFromLine(string Line)
		{
			// returns the key and the value of a key-value pair in "key=value" format.
			int EqualPos = 0;
			EqualPos = Line.IndexOf("=", 0);
			if (EqualPos < 1)
			{
				return null;
			}
			string LeftKey = Line.Substring(0, EqualPos);
			string RightValue = Line.Substring(EqualPos + 1);
			
			string[] ToReturn = {LeftKey, RightValue};
			return ToReturn;
		}

		private enum LineType // return type for IsLineACategoryDef and LineVerify
		{
			NotACategory,
			Category,
			Faulty,
			Comment,
			Empty,
			Ok
		}

		private static LineType IsLineACategoryDef(string Line)
		{
			if (Line.Length < 3)
				return LineType.NotACategory; // must be a short keyname like "k="
            
			if (Line.Substring(0,1) == "[" & Line.Substring(Line.Length - 1, 1) == "]")
				// seems to be a categorydef
			{
				if (Line.IndexOf("=") != -1) 
					//  '=' found -> is incorrect for category def
					return LineType.Faulty;
				if (ContainsMoreThanOne(Line,'[') | ContainsMoreThanOne(Line, ']'))
					// two or more '[' or ']' found -> incorrect
					return LineType.Faulty;
				return LineType.Category;
			}
			return LineType.NotACategory;
		}

		private static string[] DivideToLines(string Data)
		{		// Divides a string into lines
			string[] Lines = new string[Data.Length];
			int oldnewlinepos = 0;
			int LineCounter = 0;
			for (int i = 0; i < Data.Length; i++)
			{
				if (Data.ToCharArray(i,1)[0] == '\n')
				{
					Lines[LineCounter] = Data.Substring(oldnewlinepos, i - oldnewlinepos - 1);
					oldnewlinepos = i + 1;
					LineCounter++;
				}
			}

			// Lines[] array is too big: needs to be trimmed
			
			Lines[LineCounter] = Data.Substring(oldnewlinepos, Data.Length - oldnewlinepos);
			string[] LinesTrimmed = new string[LineCounter + 1];
			for (int i = 0; i < LineCounter + 1; i++)
			{
				LinesTrimmed[i] = Lines[i];
			}
			return LinesTrimmed;
		}

		private static bool ContainsMoreThanOne(string Data, char verify)
		{	// returns true if Data contains verify more than once
			char[] data = Data.ToCharArray();
			int count = 0;
			foreach (char c in data)
			{
				if (c == verify)
					count++;
			}
			if (count > 1)
				return true;
			return false;
		}

		private static LineType LineVerify(string line)
		{		// Verifies a line of an ini
			if (line == "")
				return LineType.Empty;

			if (line.IndexOf(";") == 0 | line.IndexOf("#") == 0 | line.IndexOf("//") == 0)
			{
				return LineType.Comment; // line is a comment: ignore
			}

			int equalindex = line.IndexOf('=');
			if (equalindex == 0)
				return LineType.Faulty; // an '=' cannot be on first place

			if (equalindex != -1) // if = is found in line
			{
				// Verify: no '[' , ']' ,';' or '#' before the '='
				if (line.IndexOf('[', 0, equalindex) != -1
					| line.IndexOf(']', 0, equalindex) != -1
					| line.IndexOf(';', 0, equalindex) != -1
                    | line.IndexOf('#', 0, equalindex) != -1)
					return LineType.Faulty;
       		}

			return LineType.Ok;
		}

		private static string[] RemoveAndVerifyIni(string[] Lines)
		{
			// removes empty lines and comments, and verifies every line
			string[] temp = new string[Lines.Length];
			int TempCounter = 0; // number of lines to return
			foreach (string line in Lines)
			{
				switch (LineVerify(line))
				{
					case LineType.Faulty: // line is faulty
						return null;
					case LineType.Comment:	//	line is a comment
						continue;
					case LineType.Ok:	// line is ok
						temp[TempCounter] = line;
						TempCounter++;
						break;
					case LineType.Empty: // line is empty
						continue;
				}
			}
			// the temp[] array is too big: needs to be trimmed.
			string[] OKLines = new string[TempCounter];
			for (int i = 0; i < TempCounter; i++)
			{
				OKLines[i] = temp[i];
			}
			return OKLines;
		}
		#endregion
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Switzerland Switzerland
I left the Microsoft world, and am now working with linux and a BSD. This switch was a big relief, mostly for my keyboard, which doesn't get slammed again.

A big thank you to those who helped me with their good articles.

Comments and Discussions