|
| 1 | +#region info |
| 2 | +/* |
| 3 | + * Ini Class - Version 1.0 |
| 4 | + * Copyright (C) 2015 Petros Kyladitis <http://www.multipetros.gr/> |
| 5 | + * |
| 6 | + * Complete INI file properties manipulation class. |
| 7 | + * |
| 8 | + * This is free software, distributed under the therms & conditions of the FreeBSD License. For the full License text |
| 9 | + * see at the 'license.txt' file, distributed with this project or at <http://www.multipetros.gr/freebsd-license/> |
| 10 | + */ |
| 11 | + #endregion |
| 12 | + |
| 13 | +using System ; |
| 14 | +using System.IO ; |
| 15 | +using System.Collections.Generic ; |
| 16 | + |
| 17 | +namespace Multipetros.Config{ |
| 18 | + /// <summary> |
| 19 | + /// Description of Ini. |
| 20 | + /// </summary> |
| 21 | + public class Ini : ConfigBase{ |
| 22 | + |
| 23 | + private Dictionary<string, Dictionary<string, string>> sections ; //store all file structure |
| 24 | + //with sections as keys & nested lines as values |
| 25 | + private Dictionary<string, string> properties ; //lines of each section stored in a dictionary with key-value pair |
| 26 | + private char[] separator = new char[1] ; //1-cell char array to store separator |
| 27 | + private string separatorStr ; //separator as string |
| 28 | + private char[] comments = new char[1] ; //1-cell char array to store comments start sign |
| 29 | + private string commentsStr ; //comments sign as string |
| 30 | + private string file ; //properties file path |
| 31 | + private bool autosave ; //autosave mode option |
| 32 | + private bool ignoreCase ; //ignore case filter for keys option |
| 33 | + |
| 34 | + /// <summary> |
| 35 | + /// The simplest constructor, with filename only to configure. Autosave properties setted as False, ingore case as True, the Separator as "=" & Comments sign as "#". |
| 36 | + /// </summary> |
| 37 | + /// <param name="file">A valid file path, with read and write priviliges</param> |
| 38 | + public Ini(string file) : this(file, false, true, '=', '#') { } |
| 39 | + |
| 40 | + /// <summary> |
| 41 | + /// Constructor with filename and autosave mode to configure. Ingore case setted as True, the Separator as "=" & Comments sign as "#". |
| 42 | + /// </summary> |
| 43 | + /// <param name="file">A valid file path, with read and write priviliges</param> |
| 44 | + /// <param name="autosave">True, to save each time a Property added or setted. False, to save manualy by calling the Save() method</param> |
| 45 | + public Ini(string file, bool autosave) : this(file, autosave, true, '=', '#') { } |
| 46 | + |
| 47 | + /// <summary> |
| 48 | + /// Constructor with filename, autosave mode & ignore case to configure. Separator setted as "=" & Comments sign as "#". |
| 49 | + /// </summary> |
| 50 | + /// <param name="file">A valid file path, with read and write priviliges</param> |
| 51 | + /// <param name="autosave">True, to save each time a Property added or setted. False, to save manualy by calling the Save() method</param> |
| 52 | + /// <param name="ignoreCase">True to ignore case, False to make properties keys case sensitive</param> |
| 53 | + public Ini(string file, bool autosave, bool ignoreCase) : this(file, autosave, ignoreCase, '=', '#') { } |
| 54 | + |
| 55 | + /// <summary> |
| 56 | + /// Constructor with filename, autosave mode, ignore case & the separator sign to configure. The Comments sign setted as "#". |
| 57 | + /// </summary> |
| 58 | + /// <param name="file">A valid file path, with read and write priviliges</param> |
| 59 | + /// <param name="autosave">True, to save each time a Property added or setted. False, to save manualy by calling the Save() method</param> |
| 60 | + /// <param name="ignoreCase">True to ignore case, False to make properties keys case sensitive</param> |
| 61 | + /// <param name="separator">Separator character for keys and values</param> |
| 62 | + public Ini(string file, bool autosave, bool ignoreCase, char separator) : this(file, autosave, ignoreCase, separator, '#') { } |
| 63 | + |
| 64 | + /// <summary> |
| 65 | + /// Constructor with filename, autosave mode, ignore case, the separator & comments sign to configure. |
| 66 | + /// </summary> |
| 67 | + /// <param name="file">A valid file path, with read and write priviliges</param> |
| 68 | + /// <param name="autosave">True, to save each time a Property added or setted. False, to save manualy by calling the Save() method</param> |
| 69 | + /// <param name="ignoreCase">True to ignore case, False to make properties keys case sensitive</param> |
| 70 | + /// <param name="separator">Separator character for keys and values</param> |
| 71 | + /// <param name="comments">Comments definition character</param> |
| 72 | + public Ini(string file, bool autosave, bool ignoreCase, char separator, char comments){ |
| 73 | + this.file = file ; |
| 74 | + this.autosave = autosave ; |
| 75 | + this.ignoreCase = ignoreCase ; |
| 76 | + this.separator[0] = separator ; |
| 77 | + this.separatorStr = separator.ToString() ; |
| 78 | + this.comments[0] = comments ; |
| 79 | + this.commentsStr = comments.ToString() ; |
| 80 | + Init() ; |
| 81 | + } |
| 82 | + |
| 83 | + /// <summary> |
| 84 | + /// Initialize the object by loading sections & properties and their values & comments from specified file |
| 85 | + /// </summary> |
| 86 | + private void Init(){ |
| 87 | + //initialize dictionary to store the INI file structure, with sections as keys |
| 88 | + //and other lines (properties & line comments) as values in nested dictionaries |
| 89 | + sections = new Dictionary<string, Dictionary<string, string>>(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal) ; |
| 90 | + if(File.Exists(file)){ //if file not exist, don't do anything else |
| 91 | + string[] lines = null ; //init a new string array to store the file lines |
| 92 | + try{ |
| 93 | + lines = File.ReadAllLines(file) ; //load lines from file, on error throws an exception |
| 94 | + }catch(Exception){ } |
| 95 | + string sectionName = "" ; //default section name is an empty string |
| 96 | + int sectionStart ; //section name start position |
| 97 | + int sectionEnd ; //section name end position |
| 98 | + string[] keyval ; //2-cell array to store key and value of each line |
| 99 | + //initilize dictionary to store the 1st founded section properties, setting the ignore case as the selected |
| 100 | + properties = new Dictionary<string, string>(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal) ; |
| 101 | + int i = 0 ; //line number counter |
| 102 | + foreach(string line in lines){ //analyze line by line |
| 103 | + if(line.Contains("[") && line.Contains("]")){ //if the chars '[' & ']' a section founded, so extract its name included in these chars |
| 104 | + |
| 105 | + |
| 106 | + if(properties.Count > 0){ //if initialized properties dictionary has values, means that a previous existed so, |
| 107 | + sections[sectionName] = properties ; //add the founded properties to the sections dictionary, with se previous section name |
| 108 | + } |
| 109 | + sectionStart = line.IndexOf("[") + 1 ; //find the start index of the section name |
| 110 | + sectionEnd = line.IndexOf("]") - 1 ; //find the last index of the section name |
| 111 | + if(sectionStart > sectionEnd){ //if ending bracket founded before starting, eg(']abc[') |
| 112 | + continue ; //ignore & go to next line |
| 113 | + } |
| 114 | + |
| 115 | + sectionName = line.Substring(sectionStart, sectionEnd) ; //get the secton name, who is inside the brackets. Any other characters just ignored. |
| 116 | + //initialize a new properties dictionary, to store the properties of the founded section |
| 117 | + properties = new Dictionary<string, string>(ignoreCase ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal) ; |
| 118 | + }else{ //this line is property or standalone comment. Analyze and extract it. |
| 119 | + if(line.Contains(separatorStr)){ //if no separator exist, this is not a property line |
| 120 | + keyval = line.Split(separator, 2) ; //split line at the 1st separator appearacne |
| 121 | + keyval[0] = keyval[0].Trim() ; //trim property's name from spaces |
| 122 | + if(properties.ContainsKey(keyval[0])){ //if key name already exist, continue to the next line |
| 123 | + continue ; //no duplicate key names allowed, in this case kept the 1st that appeared |
| 124 | + } |
| 125 | + keyval[1] = keyval[1].Trim() ; //trim property's value line from spaces |
| 126 | + properties[keyval[0]] = keyval[1] ; //put this part of the line, as value to properties dictionary of the current section |
| 127 | + }else if(line.Contains(commentsStr)){ //if there's no separator char, but only a comments sign |
| 128 | + //this is a standalone comment line. Keep the comment, wich begins at the index of the comment sign |
| 129 | + //until the end of the line. Any preceding characters are trimmed off. The comment will saved to the |
| 130 | + //current section properties dictionary, with the comment as value, and the comment sign combined with |
| 131 | + //the current line number as key (eg. #15=xxx). This, keep the key name unique. |
| 132 | + properties[commentsStr + i.ToString()] = line.Substring(line.IndexOf(comments[0])) ; |
| 133 | + } |
| 134 | + } |
| 135 | + i++ ; //increase the line counter |
| 136 | + } |
| 137 | + //store the latest founded set of properties to the last founded section |
| 138 | + if(properties.Count > 0){ |
| 139 | + sections[sectionName] = properties ; |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + /// <summary> |
| 145 | + /// Store properties to file |
| 146 | + /// </summary> |
| 147 | + public void Save(){ |
| 148 | + string output = "" ; //initilize output string |
| 149 | + //add the default section (empty name) properties line by line |
| 150 | + if(sections.ContainsKey("")){ |
| 151 | + foreach(KeyValuePair<string, string> mainKeyVal in sections[""]){ |
| 152 | + if(mainKeyVal.Key.StartsWith(commentsStr)){ |
| 153 | + //if theres is a standalone comment line add comment sign and the stored value |
| 154 | + output += commentsStr + mainKeyVal.Value ; |
| 155 | + }else{ |
| 156 | + output += mainKeyVal.Key + separatorStr + mainKeyVal.Value ; |
| 157 | + } |
| 158 | + output += "\r\n" ; |
| 159 | + } |
| 160 | + output += "\r\n" ; |
| 161 | + } |
| 162 | + //foreach named sections, add the section name and the properties to the output line by line |
| 163 | + foreach(KeyValuePair<string, Dictionary<string,string>> sectsKeyval in sections){ |
| 164 | + if(sectsKeyval.Key != ""){ |
| 165 | + //add section name, encloded in brackets |
| 166 | + output += "[" + sectsKeyval.Key + "]\r\n" ; |
| 167 | + //add the properties of the current section |
| 168 | + foreach(KeyValuePair<string, string> propsKeyval in sectsKeyval.Value){ |
| 169 | + if(propsKeyval.Key.StartsWith(commentsStr)){ |
| 170 | + //if theres is a standalone comment line add comment sign and the stored value |
| 171 | + output += propsKeyval.Value ; |
| 172 | + }else{ |
| 173 | + output += propsKeyval.Key + separatorStr + propsKeyval.Value ; |
| 174 | + |
| 175 | + } |
| 176 | + output += "\r\n" ; |
| 177 | + } |
| 178 | + output += "\r\n" ; |
| 179 | + } |
| 180 | + } |
| 181 | + File.WriteAllText(file, output) ; |
| 182 | + } |
| 183 | + |
| 184 | + /// <summary> |
| 185 | + /// Strip value part from comments |
| 186 | + /// </summary> |
| 187 | + /// <param name="val">Value text</param> |
| 188 | + /// <returns>The value striped from comments and trimmed from spaces between value and comments</returns> |
| 189 | + private string StripComments(string val){ |
| 190 | + if(val.IndexOf(comments[0]) < 0){ |
| 191 | + return val ; |
| 192 | + }else{ |
| 193 | + return val.Substring(0, val.IndexOf(comments[0])).Trim() ; |
| 194 | + } |
| 195 | + } |
| 196 | + |
| 197 | + /// <summary> |
| 198 | + /// Store a new value to the value part of the line, keeping the existed comments |
| 199 | + /// </summary> |
| 200 | + /// <param name="currentVal">Current value part of the line (with value and comments)</param> |
| 201 | + /// <param name="newVal">The new value</param> |
| 202 | + /// <returns>The new value part of the line</returns> |
| 203 | + private string StoreKeepingComments(string currentVal, string newVal){ |
| 204 | + return newVal + currentVal.Substring(StripComments(currentVal).Length) ; |
| 205 | + } |
| 206 | + |
| 207 | + /// <summary> |
| 208 | + /// Access properties like Dictionary, negotiating with the default (noname section) with the Property key name only eg: iniObj["keyName"]<br> |
| 209 | + /// When getting value of the specified property, or empty string if property not found<br> |
| 210 | + /// When setting a value for a non-existed property, the property will be automatically added.<br> |
| 211 | + /// If the setting value is <em>null</em> the property will be deleted. |
| 212 | + /// </summary> |
| 213 | + public string this[string key]{ |
| 214 | + get{ |
| 215 | + return this["", key] ; |
| 216 | + } |
| 217 | + set{ |
| 218 | + this["", key] = value ; |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + /// <summary> |
| 223 | + /// Access properties like Dictionary, negotiating with the Section & the Property key name eg: iniObj["sectionName", "keyName"]<br> |
| 224 | + /// When getting value of the specified section-property pair, or empty string if property not found<br> |
| 225 | + /// When setting a value for a non-existed section-property pair, the section-property pair will be automatically added.<br> |
| 226 | + /// If the setting value is <em>null</em> the property will be deleted. |
| 227 | + /// </summary> |
| 228 | + public string this[string section, string key]{ |
| 229 | + get{ |
| 230 | + if(sections.ContainsKey(section) && sections[section].ContainsKey(key)){ |
| 231 | + return StripComments(sections[section][key]) ; |
| 232 | + }else{ |
| 233 | + return "" ; |
| 234 | + } |
| 235 | + } |
| 236 | + set{ |
| 237 | + if(value != null){ |
| 238 | + if(!sections.ContainsKey(section)){ |
| 239 | + sections[section] = new Dictionary<string, string>() ; |
| 240 | + } |
| 241 | + if(sections[section].ContainsKey(key)){ |
| 242 | + sections[section][key] = StoreKeepingComments(sections[section][key], value) ; |
| 243 | + }else{ |
| 244 | + sections[section][key] = value ; |
| 245 | + } |
| 246 | + }else{ |
| 247 | + //if setting value is null and key exist, remove this key |
| 248 | + if(sections.ContainsKey(section) && sections[section].ContainsKey(key)){ |
| 249 | + sections[section].Remove(key) ; |
| 250 | + } |
| 251 | + } |
| 252 | + if(autosave){ |
| 253 | + Save() ; |
| 254 | + } |
| 255 | + } |
| 256 | + } |
| 257 | + } |
| 258 | +} |
0 commit comments