/* /--==###################==--\ * | 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 Virtuoso.Miranda.Plugins.Helpers { /// /// Handles Ini categories, keys and their associated values, static methods implemented for file /// handling (saving and reading) /// public class IniStructure { #region Ini structure code private SortedList Categories = new SortedList(); /// /// Initialies a new IniStructure /// public IniStructure() { } /// /// Adds a category to the IniStructure /// /// Name of the new category 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; } /// /// Deletes a category and its contents /// /// category to delete public bool DeleteCategory(string Name) { if (Name == "" | !Categories.ContainsKey(Name)) return false; Categories.Remove(Name); return true; } /// /// Renames a category /// /// Category to rename /// New name 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; } /// /// Returns the names of all categories /// /// 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; } /// /// Returns the name of a category by specifying the index. /// Useful to enumerate through all categories. /// /// The category index /// public string GetCategoryName(int Index) { if (Index < 0 | Index >= Categories.Count) return null; return Categories.GetKey(Index).ToString(); } /// /// Adds a key-value pair to a specified category /// /// Name of the category /// New name of the key /// Associated value 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; } /// /// Returns the value of a key-value pair in a specified category by specifying the key /// /// Name of the category /// Name of the Key /// 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(); } /// /// Returns the key-value pair in a specified category by specifying the index /// /// Index of the category /// Index of the Key /// 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(); } /// /// Returns the name of the key in a key-value pair in a specified category by specifying the index /// /// Index of the category /// Index of the key /// 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(); } /// /// Deletes a key-value pair /// /// Name of the category /// Name of the Key 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; } /// /// Renames the keyname in a key-value pair /// /// Name of the category /// Name of the Key /// New name of the Key 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; } /// /// Modifies the value in a key-value pair /// /// Name of the category /// Name of the Key /// New name of the Key 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; } /// /// Returns all keys in a category /// /// Name of the category /// public string[] GetKeys(string CategoryName) { SortedList Category = (SortedList)(Categories[CategoryName]); if (Category == null) return new string[0]; 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 /// /// Writes an IniStructure to a file with a comment. /// /// The contents to write /// The complete path and name of the file /// Comment to add /// public static bool WriteIni(IniStructure IniData, string Filename, string comment) { string DataToWrite = CreateData(IniData, BuildComment(comment)); return WriteFile(Filename, DataToWrite); } /// /// Writes an IniStructure to a file without a comment. /// /// The contents to write /// The complete path and name of the file /// 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 /// /// Reads an ini file and returns the content as an IniStructure. Returns null if an error occurred. /// /// The filename to read /// 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 { using (FileStream IniStream = new FileStream(filename, FileMode.Open, FileAccess.Read)) { if (!IniStream.CanRead) return null; using (StreamReader reader = new StreamReader(IniStream)) { IniData = new StringBuilder(); IniData.Append(reader.ReadToEnd()); 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 } }