/******************************* Version: 1.0 Project Boon *******************************/ using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using UnityEngine; public class INIParser { #region "Declarations" // *** Error: In case there're errors, this will changed to some value other than 1 *** // Error codes: // 1: Null TextAsset public int error = 0; // *** Lock for thread-safe access to file and local cache *** private object m_Lock = new object(); // *** File name *** private string m_FileName = null; public string FileName { get { return m_FileName; } } // ** String represent Ini private string m_iniString = null; public string iniString { get { return m_iniString; } } // *** Automatic flushing flag *** private bool m_AutoFlush = false; // *** Local cache *** private Dictionary> m_Sections = new Dictionary>(); private Dictionary> m_Modified = new Dictionary>(); // *** Local cache modified flag *** private bool m_CacheModified = false; #endregion #region "Methods" // *** Open ini file by path *** public void Open(string path) { m_FileName = path; if (File.Exists(m_FileName)) { m_iniString = File.ReadAllText(m_FileName); } else { //If file does not exist, create one var temp = File.Create(m_FileName); temp.Close(); m_iniString = ""; } Initialize(m_iniString, false); } // *** Open ini file by TextAsset: All changes is saved to local storage *** public void Open(TextAsset name) { if (name == null) { // In case null asset, treat as opened an empty file error = 1; m_iniString = ""; m_FileName = null; Initialize(m_iniString, false); } else { m_FileName = Application.persistentDataPath + name.name; //*** Find the TextAsset in the local storage first *** if (File.Exists(m_FileName)) { m_iniString = File.ReadAllText(m_FileName); } else m_iniString = name.text; Initialize(m_iniString, false); } } // *** Open ini file from string *** public void OpenFromString(string str) { m_FileName = null; Initialize(str, false); } // *** Get the string content of ini file *** public override string ToString() { return m_iniString; } private void Initialize(string iniString, bool AutoFlush) { m_iniString = iniString; m_AutoFlush = AutoFlush; Refresh(); } // *** Close, save all changes to ini file *** public void Close() { lock (m_Lock) { PerformFlush(); //Clean up memory m_FileName = null; m_iniString = null; } } // *** Parse section name *** private string ParseSectionName(string Line) { if (!Line.StartsWith("[")) return null; if (!Line.EndsWith("]")) return null; if (Line.Length < 3) return null; return Line.Substring(1, Line.Length - 2); } // *** Parse key+value pair *** private bool ParseKeyValuePair(string Line, ref string Key, ref string Value) { // *** Check for key+value pair *** int i; if ((i = Line.IndexOf('=')) <= 0) return false; int j = Line.Length - i - 1; Key = Line.Substring(0, i).Trim(); if (Key.Length <= 0) return false; Value = (j > 0) ? (Line.Substring(i + 1, j).Trim()) : (""); return true; } // *** If a line is neither SectionName nor key+value pair, it's a comment *** private bool isComment(string Line) { string tmpKey = null, tmpValue = null; if (ParseSectionName(Line) != null) return false; if (ParseKeyValuePair(Line, ref tmpKey, ref tmpValue)) return false; return true; } // *** Read file contents into local cache *** private void Refresh() { lock (m_Lock) { StringReader sr = null; try { // *** Clear local cache *** m_Sections.Clear(); m_Modified.Clear(); // *** String Reader *** sr = new StringReader(m_iniString); // *** Read up the file content *** Dictionary CurrentSection = null; string s; string SectionName; string Key = null; string Value = null; while ((s = sr.ReadLine()) != null) { s = s.Trim(); // *** Check for section names *** SectionName = ParseSectionName(s); if (SectionName != null) { // *** Only first occurrence of a section is loaded *** if (m_Sections.ContainsKey(SectionName)) { CurrentSection = null; } else { CurrentSection = new Dictionary(); m_Sections.Add(SectionName, CurrentSection); } } else if (CurrentSection != null) { // *** Check for key+value pair *** if (ParseKeyValuePair(s, ref Key, ref Value)) { // *** Only first occurrence of a key is loaded *** if (!CurrentSection.ContainsKey(Key)) { CurrentSection.Add(Key, Value); } } } } } finally { // *** Cleanup: close file *** if (sr != null) sr.Close(); sr = null; } } } private void PerformFlush() { // *** If local cache was not modified, exit *** if (!m_CacheModified) return; m_CacheModified = false; // *** Copy content of original iniString to temporary string, replace modified values *** StringWriter sw = new StringWriter(); try { Dictionary CurrentSection = null; Dictionary CurrentSection2 = null; StringReader sr = null; try { // *** Open the original file *** sr = new StringReader(m_iniString); // *** Read the file original content, replace changes with local cache values *** string s; string SectionName; string Key = null; string Value = null; bool Unmodified; bool Reading = true; bool Deleted = false; string Key2 = null; string Value2 = null; StringBuilder sb_temp; while (Reading) { s = sr.ReadLine(); Reading = (s != null); // *** Check for end of iniString *** if (Reading) { Unmodified = true; s = s.Trim(); SectionName = ParseSectionName(s); } else { Unmodified = false; SectionName = null; } // *** Check for section names *** if ((SectionName != null) || (!Reading)) { if (CurrentSection != null) { // *** Write all remaining modified values before leaving a section **** if (CurrentSection.Count > 0) { // *** Optional: All blank lines before new values and sections are removed **** sb_temp = sw.GetStringBuilder(); while ((sb_temp[sb_temp.Length - 1] == '\n') || (sb_temp[sb_temp.Length - 1] == '\r')) { sb_temp.Length = sb_temp.Length - 1; } sw.WriteLine(); foreach (string fkey in CurrentSection.Keys) { if (CurrentSection.TryGetValue(fkey, out Value)) { sw.Write(fkey); sw.Write('='); sw.WriteLine(Value); } } sw.WriteLine(); CurrentSection.Clear(); } } if (Reading) { // *** Check if current section is in local modified cache *** if (!m_Modified.TryGetValue(SectionName, out CurrentSection)) { CurrentSection = null; } } } else if (CurrentSection != null) { // *** Check for key+value pair *** if (ParseKeyValuePair(s, ref Key, ref Value)) { if (CurrentSection.TryGetValue(Key, out Value)) { // *** Write modified value to temporary file *** Unmodified = false; CurrentSection.Remove(Key); sw.Write(Key); sw.Write('='); sw.WriteLine(Value); } } } // ** Check if the section/key in current line has been deleted *** if (Unmodified) { if (SectionName != null) { if (!m_Sections.ContainsKey(SectionName)) { Deleted = true; CurrentSection2 = null; } else { Deleted = false; m_Sections.TryGetValue(SectionName, out CurrentSection2); } } else if (CurrentSection2 != null) { if (ParseKeyValuePair(s, ref Key2, ref Value2)) { if (!CurrentSection2.ContainsKey(Key2)) Deleted = true; else Deleted = false; } } } // *** Write unmodified lines from the original iniString *** if (Unmodified) { if (isComment(s)) sw.WriteLine(s); else if (!Deleted) sw.WriteLine(s); } } // *** Close string reader *** sr.Close(); sr = null; } finally { // *** Cleanup: close string reader *** if (sr != null) sr.Close(); sr = null; } // *** Cycle on all remaining modified values *** foreach (KeyValuePair> SectionPair in m_Modified) { CurrentSection = SectionPair.Value; if (CurrentSection.Count > 0) { sw.WriteLine(); // *** Write the section name *** sw.Write('['); sw.Write(SectionPair.Key); sw.WriteLine(']'); // *** Cycle on all key+value pairs in the section *** foreach (KeyValuePair ValuePair in CurrentSection) { // *** Write the key+value pair *** sw.Write(ValuePair.Key); sw.Write('='); sw.WriteLine(ValuePair.Value); } CurrentSection.Clear(); } } m_Modified.Clear(); // *** Get result to iniString *** m_iniString = sw.ToString(); sw.Close(); sw = null; // ** Write iniString to file *** if (m_FileName != null) { File.WriteAllText(m_FileName, m_iniString); } } finally { // *** Cleanup: close string writer *** if (sw != null) sw.Close(); sw = null; } } // *** Check if the section exists *** public bool IsSectionExists(string SectionName) { return m_Sections.ContainsKey(SectionName); } // *** Check if the key exists *** public bool IsKeyExists(string SectionName, string Key) { Dictionary Section; // *** Check if the section exists *** if (m_Sections.ContainsKey(SectionName)) { m_Sections.TryGetValue(SectionName, out Section); // If the key exists return Section.ContainsKey(Key); } else return false; } // *** Delete a section in local cache *** public void SectionDelete(string SectionName) { // *** Delete section if exists *** if (IsSectionExists(SectionName)) { lock (m_Lock) { m_CacheModified = true; m_Sections.Remove(SectionName); //Also delete in modified cache if exist m_Modified.Remove(SectionName); // *** Automatic flushing : immediately write any modification to the file *** if (m_AutoFlush) PerformFlush(); } } } // *** Delete a key in local cache *** public void KeyDelete(string SectionName, string Key) { Dictionary Section; //Delete key if exists if (IsKeyExists(SectionName, Key)) { lock (m_Lock) { m_CacheModified = true; m_Sections.TryGetValue(SectionName, out Section); Section.Remove(Key); //Also delete in modified cache if exist if (m_Modified.TryGetValue(SectionName, out Section)) Section.Remove(SectionName); // *** Automatic flushing : immediately write any modification to the file *** if (m_AutoFlush) PerformFlush(); } } } // *** Read a value from local cache *** public string ReadValue(string SectionName, string Key, string DefaultValue) { lock (m_Lock) { // *** Check if the section exists *** Dictionary Section; if (!m_Sections.TryGetValue(SectionName, out Section)) return DefaultValue; // *** Check if the key exists *** string Value; if (!Section.TryGetValue(Key, out Value)) return DefaultValue; // *** Return the found value *** return Value; } } // *** Insert or modify a value in local cache *** public void WriteValue(string SectionName, string Key, string Value) { lock (m_Lock) { // *** Flag local cache modification *** m_CacheModified = true; // *** Check if the section exists *** Dictionary Section; if (!m_Sections.TryGetValue(SectionName, out Section)) { // *** If it doesn't, add it *** Section = new Dictionary(); m_Sections.Add(SectionName, Section); } // *** Modify the value *** if (Section.ContainsKey(Key)) Section.Remove(Key); Section.Add(Key, Value); // *** Add the modified value to local modified values cache *** if (!m_Modified.TryGetValue(SectionName, out Section)) { Section = new Dictionary(); m_Modified.Add(SectionName, Section); } if (Section.ContainsKey(Key)) Section.Remove(Key); Section.Add(Key, Value); // *** Automatic flushing : immediately write any modification to the file *** if (m_AutoFlush) PerformFlush(); } } // *** Encode byte array *** private string EncodeByteArray(byte[] Value) { if (Value == null) return null; StringBuilder sb = new StringBuilder(); foreach (byte b in Value) { string hex = Convert.ToString(b, 16); int l = hex.Length; if (l > 2) { sb.Append(hex.Substring(l - 2, 2)); } else { if (l < 2) sb.Append("0"); sb.Append(hex); } } return sb.ToString(); } // *** Decode byte array *** private byte[] DecodeByteArray(string Value) { if (Value == null) return null; int l = Value.Length; if (l < 2) return new byte[] { }; l /= 2; byte[] Result = new byte[l]; for (int i = 0; i < l; i++) Result[i] = Convert.ToByte(Value.Substring(i * 2, 2), 16); return Result; } // *** Getters for various types *** public bool ReadValue(string SectionName, string Key, bool DefaultValue) { string StringValue = ReadValue(SectionName, Key, DefaultValue.ToString(System.Globalization.CultureInfo.InvariantCulture)); int Value; if (int.TryParse(StringValue, out Value)) return (Value != 0); return DefaultValue; } public int ReadValue(string SectionName, string Key, int DefaultValue) { string StringValue = ReadValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); int Value; if (int.TryParse(StringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out Value)) return Value; return DefaultValue; } public long ReadValue(string SectionName, string Key, long DefaultValue) { string StringValue = ReadValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); long Value; if (long.TryParse(StringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out Value)) return Value; return DefaultValue; } public double ReadValue(string SectionName, string Key, double DefaultValue) { string StringValue = ReadValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); double Value; if (double.TryParse(StringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out Value)) return Value; return DefaultValue; } public byte[] ReadValue(string SectionName, string Key, byte[] DefaultValue) { string StringValue = ReadValue(SectionName, Key, EncodeByteArray(DefaultValue)); try { return DecodeByteArray(StringValue); } catch (FormatException) { return DefaultValue; } } public DateTime ReadValue(string SectionName, string Key, DateTime DefaultValue) { string StringValue = ReadValue(SectionName, Key, DefaultValue.ToString(CultureInfo.InvariantCulture)); DateTime Value; if (DateTime.TryParse(StringValue, CultureInfo.InvariantCulture, DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AssumeLocal, out Value)) return Value; return DefaultValue; } // *** Setters for various types *** public void WriteValue(string SectionName, string Key, bool Value) { WriteValue(SectionName, Key, (Value) ? ("1") : ("0")); } public void WriteValue(string SectionName, string Key, int Value) { WriteValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); } public void WriteValue(string SectionName, string Key, long Value) { WriteValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); } public void WriteValue(string SectionName, string Key, double Value) { WriteValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); } public void WriteValue(string SectionName, string Key, byte[] Value) { WriteValue(SectionName, Key, EncodeByteArray(Value)); } public void WriteValue(string SectionName, string Key, DateTime Value) { WriteValue(SectionName, Key, Value.ToString(CultureInfo.InvariantCulture)); } #endregion }