/ConfigFileManager/ConfigFile.cs
C# | 917 lines | 726 code | 16 blank | 175 comment | 175 complexity | e641af7d44bf5e8cf4967851b9311052 MD5 | raw file
- /*
- COPYRIGHT NOTICE
-
- Copyright (c) 2011, S James S Stapleton
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without modification,
- are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.IO;
-
- namespace ConfigFileManager
- {
- public static class ConfigFile
- {
- static private Encoding c_default_encoding = Encoding.Default /*Encoding.UTF8*/;
- /// <summary>
- /// Get/set the default encoding used for reading/writing files.
- /// Uses the system default initially. Note: switching encodings is not thread safe.
- /// </summary>
- static public Encoding default_encoding
- {
- get
- {
- return c_default_encoding;
- }
- set
- {
- c_default_encoding = value;
- }
- }
- /// <summary>
- /// Set the ConfigFile tool to use the system default encoding. Note: switching encodings is not thread safe.
- /// </summary>
- static public void SystemDefaultEncoding()
- {
- c_default_encoding = Encoding.Default;
- }
- /// <summary>
- /// Honor bom? to determine encoding. Set to false to always use the default/set encoding.
- /// </summary>
- static public bool honor_bom = true;
-
- static public readonly int c_version_major = 0;
- static public readonly int c_version_minor = 4;
- static public readonly int c_version_build = 9;
- static public readonly string c_version_string = "ConfigFileManager Version " + c_version_major.ToString() +
- "." + c_version_minor.ToString() + "." + c_version_build.ToString() + " (alpha)";
-
- static public int c_line_length = 80;
- /// <summary>
- /// Get/Set the length of lines in Write()'ed files
- /// </summary>
- static public int line_length
- {
- get
- {
- return c_line_length;
- }
- set
- {
- c_line_length = value;
- }
- }
- /// <summary>
- /// New line characters, for splitting strings
- /// </summary>
- static public readonly string[] c_newlines = new string[] { "\r\n", "\n", "\r" };
- /// <summary>
- /// Whitespace, used for acceptable line splits
- /// </summary>
- static public readonly char[] c_whitespace = new char[] { ' ', '\t', '\v', '(', ')', '[', ']', '{', '}' };
-
- /// <summary>
- /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
- /// </summary>
- /// <param name="fname">The name of the file the entries were read from.</param>
- /// <param name="outd">The configuration file is placed into outd.</param>
- /// <param name="lines">The file content, split by lines, to parse into outd.</param>
- static public void Read(string fname, ref Dictionary<string, string> outd, string[] lines)
- {
- string key;
- string val;
- bool append;
- int i = 0;
- while(GetNextKeyValue(fname, ref i, lines, out append, out key, out val))
- {
- if(append && outd.ContainsKey(key))
- outd[key]+= val;
- else
- outd[key] = val;
- }
- }
- /// <summary>
- /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
- /// </summary>
- /// <param name="fname">The name of the file the entries were read from. It will be opened and read</param>
- /// <param name="outd">The configuration file is placed into outd.</param>
- static public void Read(string fname, ref Dictionary<string, string> outd)
- {
- using(FileStream fp = new FileStream(fname, FileMode.Open, FileAccess.Read))
- {
- byte[] readbuff = new byte[fp.Length];
- int pos = 0;
- while(pos < fp.Length)
- pos += fp.Read(readbuff, pos, (int)fp.Length-pos);
- Encoding enc = c_default_encoding;
- //try to honor BOM
- if(honor_bom)
- {
- get_decoder(ref readbuff, ref enc);
- }
- string buff = enc.GetString(readbuff);
- string[] lines = buff.Split(c_newlines, StringSplitOptions.None);
- Read(fname, ref outd, lines);
- }
- }
- /// <summary>
- /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
- /// </summary>
- /// <param name="fname">The name of the file the entries were read from.</param>
- /// <param name="keyorder">The order of the keys, read from the file, </param>
- /// <param name="outd">The configuration file is placed into outd.</param>
- /// <param name="lines">The file content, split by lines, to parse into outd.</param>
- /// <param name="comments">Comments are stored here. Set passed arg to null to not read comments.</param>
- static public void Read(string fname, out string[] keyorder, ref Dictionary<string, string> outd, ref Dictionary<string,string> comments, string[] lines)
- {
- List<string> keyo = new List<string>();
- string key;
- string val;
- bool append;
- string comment;
- int i = 0;
- while(GetNextKeyValue(fname, ref i, lines, out append, out key, out val, out comment))
- {
- if(append && outd.ContainsKey(key))
- outd[key] += val;
- else
- outd[key] = val;
- if(!keyo.Contains(key)) keyo.Add(key);
-
- if(comments != null)
- {
- if(append && comments.ContainsKey(key))
- comments[key] += comment;
- else
- comments[key] = comment;
- }
- }
- keyorder = keyo.ToArray<string>();
- }
- /// <summary>
- /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
- /// </summary>
- /// <param name="fname">The name of the file the entries were read from. It will be opened and read</param>
- /// <param name="keyorder">The order of the keys, read from the file, </param>
- /// <param name="outd">The configuration file is placed into outd.</param>
- /// <param name="comments">Comments are stored here. Set passed arg to null to not read comments.</param>
- static public void Read(string fname, out string[] keyorder, ref Dictionary<string, string> outd, ref Dictionary<string,string> comments)
- {
- List<string> keyo = new List<string>();
- using(FileStream fp = new FileStream(fname, FileMode.Open, FileAccess.Read))
- {
- byte[] readbuff = new byte[fp.Length];
- int pos = 0;
- while(pos < fp.Length)
- pos += fp.Read(readbuff, pos, (int)fp.Length - pos);
- Encoding enc = c_default_encoding;
- //try to honor BOM
- if(honor_bom)
- {
- get_decoder(ref readbuff, ref enc);
- }
- string buff = enc.GetString(readbuff);
- string[] lines = buff.Split(c_newlines, StringSplitOptions.None);
- Read(fname, out keyorder, ref outd, ref comments, lines);
- }
- }
- /// <summary>
- /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
- /// </summary>
- /// <param name="fname">The name of the file the entries were read from.</param>
- /// <param name="keyorder">The order of the keys, read from the file, </param>
- /// <param name="outd">The configuration file is placed into outd.</param>
- /// <param name="lines">The file content, split by lines, to parse into outd.</param>
- /// <param name="comments">Comments are stored here. There will be one entry per key. If left null, comments won't be retrieved.</param>
- static public void Read(string fname, out IList<string> keyorder, ref Dictionary<string, IList<string>> outd, ref Dictionary<string, IList<string>> comments, string[] lines)
- {
- outd.Clear();
- if(comments != null) comments.Clear();
- keyorder = new List<string>();
- string key;
- string val;
- bool append;
- string comment;
- int i = 0;
- while(GetNextKeyValue(fname, ref i, lines, out append, out key, out val, out comment))
- {
- if(!append || !outd.ContainsKey(key))
- outd[key] = new List<string>();
- outd[key].Add(val);
-
- if(comments != null)
- {
- if(comment==null)comment="";
- if(!append || !comments.ContainsKey(key))
- comments[key] = new List<string>();
- comments[key].Add(comment);
- }
- ((List<string>)keyorder).Add(key);
- }
- }
- /// <summary>
- /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
- /// </summary>
- /// <param name="fname">The name of the file the entries were read from. It will be opened and read</param>
- /// <param name="keyorder">The order of the keys, read from the file, </param>
- /// <param name="outd">The configuration file is placed into outd.</param>
- /// <param name="comments">Comments are stored here. Set passed arg to null to not read comments.</param>
- static public void Read(string fname, out IList<string> keyorder, ref Dictionary<string, IList<string>> outd, ref Dictionary<string, IList<string>> comments)
- {
- List<string> keyo = new List<string>();
- using(FileStream fp = new FileStream(fname, FileMode.Open, FileAccess.Read))
- {
- byte[] readbuff = new byte[fp.Length];
- int pos = 0;
- while(pos < fp.Length)
- pos += fp.Read(readbuff, pos, (int)fp.Length - pos);
- Encoding enc = c_default_encoding;
- //try to honor BOM
- if(honor_bom)
- {
- get_decoder(ref readbuff, ref enc);
- }
- string buff = enc.GetString(readbuff);
- string[] lines = buff.Split(c_newlines, StringSplitOptions.None);
- Read(fname, out keyorder, ref outd, ref comments, lines);
- }
- }
- /// <summary>
- /// Writes a configuration file. Any carrage return, newline, and carrage return + newlines found in keys or
- /// values will be replaced by newlines when they are escaped. I.E. The following C# string:
- /// "\n \r \r\n" will be written to file as this C# string "\\n \\n \\n", or if opened a text editor, this exact
- /// string: `\n \n \n`.
- /// </summary>
- /// <param name="fname">The name of the output file</param>
- /// <param name="pairs">The configuration to write</param>
- /// <param name="comments">Comments. Each comment is given a key, the comment is written before that value.</param>
- /// <param name="order">Write keys in this order. Keys not in this list will appear later, in arbitrary order</param>
- static public void Write(string fname, Dictionary<string, string> pairs, Dictionary<string, string> comments, string[] order)
- {
- //step one, generate the output to write.
- StringBuilder output = new StringBuilder();
- string[] entry;
- if(order != null && order.Length > 0)
- {
- for(int i = 0; i < order.Length; i++)
- {
- entry = GetEntry(order[i], pairs, comments);
- foreach(string s in entry)
- output.Append(s);
- output.Append(Environment.NewLine);
- }
- }
- foreach(string s in pairs.Keys)
- {
- if(order.Contains<string>(s)) continue;
- entry = GetEntry(s, pairs, comments);
- foreach(string st in entry)
- output.Append(st);
- output.Append(Environment.NewLine);
- }
-
- byte[] outp = c_default_encoding.GetBytes(output.ToString());
- output = null;
-
- using(FileStream fp = new FileStream(fname, FileMode.Create, FileAccess.Write))
- {
- fp.Write(outp, 0, outp.Length);
- }
- }
- /// <summary>
- /// Writes a configuration file. Any carrage return, newline, and carrage return + newlines found in keys or
- /// values will be replaced by newlines when they are escaped. I.E. The following C# string:
- /// "\n \r \r\n" will be written to file as this C# string "\\n \\n \\n", or if opened a text editor, this exact
- /// string: `\n \n \n`.
- /// </summary>
- /// <param name="fname">The name of the output file</param>
- /// <param name="pairs">The configuration to write</param>
- /// <param name="comments">Comments. Each comment is given a key, the comment is written before that value.</param>
- /// <param name="order">Write keys in this order. Keys not in this list will appear later, in arbitrary order</param>
- static public void Write(string fname, Dictionary<string, IList<string>> pairs, Dictionary<string, IList<string>> comments, IList<string> order)
- {
- //step one, generate the output to write.
- StringBuilder output = new StringBuilder();
- Dictionary<string, int> saved_to = new Dictionary<string, int>(pairs.Count);
- foreach(string key in pairs.Keys)
- {
- saved_to[key] = 0;
- }
- string[] entry;
- if(order != null && order.Count > 0)
- {
- for(int i = 0; i < order.Count; i++)
- {
- string key = order[i];
- int p = saved_to[key];
- entry = GetEntry(key, p, pairs, comments);
- foreach(string s in entry)
- output.Append(s);
- output.Append(Environment.NewLine);
- saved_to[key]++;
- }
- }
- foreach(string s in pairs.Keys)
- {
- int p = saved_to[s];
- while(p < pairs[s].Count)
- {
- entry = GetEntry(s, p, pairs, comments);
- foreach(string st in entry)
- output.Append(st);
- output.Append(Environment.NewLine);
- p++;
- }
- }
-
- byte[] outp = c_default_encoding.GetBytes(output.ToString());
- output = null;
-
- using(FileStream fp = new FileStream(fname, FileMode.Create, FileAccess.Write))
- {
- fp.Write(outp, 0, outp.Length);
- }
- }
- /// <summary>
- /// Get the output config lines for the config file.
- /// Note that comments are not escaped, but values and keys are!
- /// </summary>
- /// <param name="key">The config file key for the entry of interest</param>
- /// <param name="pairs">The config key/value file values</param>
- /// <param name="comments">The comments for some keys of interest</param>
- /// <returns>The lines for the specified entry in a config file</returns>
- static private string[] GetEntry(string key, Dictionary<string, string> pairs, Dictionary<string, string> comments)
- {
- List<string> ret = new List<string>();
- string val = null;
- string[] vals;
- if(comments != null && comments.TryGetValue(key, out val) && val != null)
- {
- if(c_line_length > 10)
- {
- vals = SplitLine(val, c_line_length - 2);
- foreach(string v in vals)
- if(v != null)
- ret.Add(string.Format("# {0}{1}", v, Environment.NewLine));
- }
- else
- ret.Add(string.Format("# {0}{1}", val, Environment.NewLine));
- }
- val=pairs[key];
- if(val==null)
- return new string[] { "" };
- val = Escape(key) + "=" + Escape(val);
- vals = SplitLine(val, c_line_length);
- int i=0;
- foreach(string v in vals)
- {
- i++;
- string va = v;
- char tc=va[0];
- if(char.IsWhiteSpace(tc) || tc == '#' || tc == '_') va = "_" + va;
- tc = va[va.Length-1];
- if(i < vals.Length) va += '\\';
- else if(char.IsWhiteSpace(tc) || tc == '_') va += "_";
- ret.Add(string.Format("{0}{1}", va, Environment.NewLine));
- }
- return ret.ToArray();
- }
- /// <summary>
- /// Get the output config lines for the config file. Each value can have multiple assigned strings.
- /// Note that comments are not escaped, but values and keys are!
- /// </summary>
- /// <param name="key">The config file key for the entry of interest</param>
- /// <param name="pos">The position of the desired string within pairs/comments.</param>
- /// <param name="pairs">The config key/value file values</param>
- /// <param name="comments">The comments for some keys of interest.</param>
- /// <returns>The lines for the specified entry in a config file. Any given value in comments must have the
- /// same count as the value assigned to a similar key in pairs.</returns>
- static private string[] GetEntry(string key, int pos, Dictionary<string, IList<string>> pairs, Dictionary<string, IList<string>> comments)
- {
- List<string> ret = new List<string>();
- IList<string> val = null;
- string va = pairs[key][pos];
- if(va==null)
- {
- return new string[] { "" };
- }
- string[] vals;
- if(comments != null && comments.TryGetValue(key, out val) && val != null)
- {
- if(val[pos].Length > 0)
- {
- if(c_line_length > 10)
- {
- vals = SplitLine(val[pos], c_line_length - 2);
- foreach(string v in vals)
- if(v!=null)
- ret.Add(string.Format("# {0}{1}", v, Environment.NewLine));
- }
- else
- ret.Add(string.Format("# {0}{1}", val[pos], Environment.NewLine));
- }
- }
- string eq;
- if(pos == 0) eq = "=";
- else eq = "+=";
- va = Escape(key) + eq + Escape(va);
- vals = SplitLine(va, c_line_length);
- int i = 0;
- foreach(string v in vals)
- {
- i++;
- va = v;
- char tc=va[0];
- if(char.IsWhiteSpace(tc)||tc=='#'||tc=='_') va="_"+va;
- tc=va[va.Length-1];
- if(i < vals.Length) va += "\\";
- else if(char.IsWhiteSpace(tc) || tc == '_') va += "_";
- ret.Add(string.Format("{0}{1}", va, Environment.NewLine));
- }
- return ret.ToArray();
- }
- /// <summary>
- /// A utility function for helping with escaped strings - counts the number fo slashes prior to an index.
- /// </summary>
- /// <param name="s">The string of interest</param>
- /// <param name="index">The index to start counting from (actually, index-1).</param>
- /// <returns></returns>
- static public int CountPreSlashes(string s, int index)
- {
- int j = 0;
- for(j = index - 1; j >= 0 && s[j] == '/'; j--) ;
- return index - j - 1;
- }
- /// <summary>
- /// unescapes strings
- /// \n -> newline
- /// \r -> carrage return
- /// \f -> form feed
- /// \\ -> slash
- /// \t -> horiz. tab
- /// \v -> vert. tab
- /// \[NEWLINE] -> ignored, newline removed
- /// \0x## -> ASCII ##
- /// \u#### -> Unicode ####
- /// </summary>
- /// <param name="s">The string to unescape</param>
- /// <returns>The unescaped string</returns>
- static public string UnEscapeString(string s)
- {
- List<object> ret = new List<object>();
- int st = 0;
- for(int i = 0; i < s.Length; i++)
- {
- if(s[i] == '\\')
- {
- if(i != st)
- {
- ret.Add(s.Substring(st, i-st));
- }
- i++;
- if(i == s.Length)
- {
- throw new Exception("UnEscaping string, non-escaping '\\' at End of String");
- }
- char c = s[i];
- switch(c)
- {
- case 'n':
- ret.Add(Environment.NewLine);
- break;
- case 'r':
- if(s.Length >= i + 2 && s[i + 1] == '\\' && s[i + 2] == 'n')
- {
- i+=2;
- ret.Add(Environment.NewLine);
- }
- else
- ret.Add(Environment.NewLine);
- break;
- case '\\':
- ret.Add("\\");
- break;
- case 'f':
- ret.Add("\f");
- break;
- case 't':
- ret.Add("\t");
- break;
- case 'v':
- ret.Add("\v");
- break;
- case '0':
- if(i + 2 < s.Length && (s[i] == 'x' || s[i] == 'X'))
- {
- ret.Add((byte)Convert.ToByte(s.Substring(i + 1, i + 3)));
- i += 2;
- break;
- }
- throw new Exception("Error in escaping string, '\\0' must be followed by 'x##', where ## is two numbers.");
- case 'u':
- if(i + 4 < s.Length && (s[i] == 'u' || s[i] == 'U'))
- {
- ret.Add((byte)Convert.ToByte(s.Substring(i + 1, i + 3)));
- ret.Add((byte)Convert.ToByte(s.Substring(i + 3, i + 5)));
- i += 4;
- break;
- }
- throw new Exception("Error in escaping string '\\u' must be followed by '####', where #### is for numbers.");
- default:
- ret.Add(s[i]);
- break;
- }
- st = i + 1;
- }
- }
- if(st < s.Length)
- {
- ret.Add(s.Substring(st));
- }
- //merge the string
- if(ret.Count == 1 && ret[0].GetType() == typeof(string))
- return (string)ret[0];
- StringBuilder sb = new StringBuilder();
- for(int i = 0; i < ret.Count; i++)
- {
- object o = ret[i];
- if(o.GetType() == typeof(string))
- {
- sb.Append((string)o);
- continue;
- }
- int start = i;
- while(i < ret.Count && ret[i].GetType() == typeof(byte))
- {
- i++;
- }
- byte[] buff = new byte[i-start];
- for(int j = st; j < i; j++)
- buff[j-st] = (byte)ret[j];
- sb.Append(Encoding.UTF8.GetString(buff));
- }
- return sb.ToString();
- }
- /// <summary>
- /// This gets the next key/value from the lines loaded from a config file.
- /// </summary>
- /// <param name="file">The name of the file - for printing errors.</param>
- /// <param name="i">The line to start looking, in the file, set to the line past the last line loaded.</param>
- /// <param name="lines">The lines in the file.</param>
- /// <param name="append">This is set to true, if the the key/value is to be appended.</param>
- /// <param name="key">This is set to the key found.</param>
- /// <param name="value">This is set to the value found.</param>
- /// <returns>True if a key/value is found, otherwise false.</returns>
- static public bool GetNextKeyValue(string file, ref int i, string[] lines, out bool append, out string key, out string value)
- {
- int s = i;
- key = null;
- value = null;
- append = false;
- string line = "";
- //get the full line
- while(true)
- {
- if(i == lines.Length)
- {
- if(line == "") return false;
- else break;
- }
- string txt = lines[i];
- txt = lines[i];
- txt = txt.Trim();
- i++;
- if(txt.Length == 0) continue; //ignore blank lines.
- if(txt.Substring(0, 1) == "#") continue; //skip comment lines
- if(txt.Substring(0, 1) == "_") txt = txt.Substring(1);
- if(txt.Substring(txt.Length - 1) == "_") txt = txt.Substring(0, txt.Length - 1);
- line += txt;
- if(line.Substring(line.Length - 1) == "\\") line = line.Substring(0, line.Length - 1);
- else break;
- }
- //we have the line, find the assignment
- bool found = false;
- int eq = 0;
- int t1 = 0;
- int t2 = 0;
- while(!found)
- {
- t1 = line.IndexOf('=', eq);
- t2 = line.IndexOf("+=", eq);
- if(t1 < 0 && t2 < 0) throw new Exception("Key/Value without an assignment: " + file + "@" + s.ToString());
- if(t1 > 0 && t2 > 0) eq = Math.Min(t1, t2);
- else eq = Math.Max(t1, t2);
- int ti = eq - 1;
- bool unescaped = true;
- while(ti >= 0 && line[eq] == '\\')
- {
- unescaped = !unescaped;
- ti--;
- }
- if(unescaped)
- {
- found = true;
- if(line[eq] == '+')
- {
- s = eq + 2;
- append = true;
- }
- else s = eq + 1;
- }
- else
- {
- if(line[eq] == '+') eq += 2;
- else eq++;
- }
- }
- key = line.Substring(0, eq);
- value = line.Substring(s);
- key = UnEscapeString(key);
- value = UnEscapeString(value);
- return true;
- }
- /// <summary>
- /// This gets the next key/value from the lines loaded from a config file.
- /// </summary>
- /// <param name="file">The name of the file - for printing errors.</param>
- /// <param name="i">The line to start looking, in the file, set to the line past the last line loaded.</param>
- /// <param name="lines">The lines in the file.</param>
- /// <param name="append">This is set to true, if the the key/value is to be appended.</param>
- /// <param name="key">This is set to the key found.</param>
- /// <param name="value">This is set to the value found.</param>
- /// <returns>True if a key/value is found, otherwise false.</returns>
- static public bool GetNextKeyValue(string file, ref int i, string[] lines, out bool append, out string key, out string value, out string comment)
- {
- int s = i;
- key = null;
- value = null;
- comment = null;
- append = false;
- string line = "";
- //get the full line
- bool cfirst = true;
- while(true)
- {
- if(i == lines.Length)
- {
- if(line == "") return false;
- else break;
- }
- string txt = lines[i];
- txt = lines[i];
- txt = txt.Trim();
- i++;
- if(txt.Length == 0) continue; //ignore blank lines.
- if(txt.Substring(0, 1) == "#") // read comments
- {
- if(comment == null)
- {
- comment = "";
- }
- string c = txt.Substring(1);
- if(c.Length > 0 && c[0] == ' ') c = c.Substring(1);
- //check for/handle multi-newlines
- if(c.Length == 0)
- {
- if(comment.Length > 0 && comment[comment.Length - 1] != '\n' && comment[comment.Length - 1] != '\r')
- comment += Environment.NewLine;
- comment += Environment.NewLine;
- }
- //insert a a space in a comment line break between two non-whitespace, non-dash, non-underscore
- char t;
- if(c.Length > 0 && !char.IsWhiteSpace(t=c[0]) && t != '-' && t != '_' &&
- (comment.Length > 0 && !char.IsWhiteSpace(t=comment[comment.Length - 1]) && t!='-' && t != '_')) comment += " ";
- comment += c;
- continue;
- }
- if(txt.Substring(0, 1) == "_") txt = txt.Substring(1);
- if(txt.Substring(txt.Length - 1) == "_") txt = txt.Substring(0, txt.Length - 1);
- line += txt;
- if(line.Substring(line.Length - 1) == "\\") line = line.Substring(0, line.Length - 1);
- else break;
- }
- //we have the line, find the assignment
- bool found = false;
- int eq = 0;
- int t1 = 0;
- int t2 = 0;
- while(!found)
- {
- t1 = line.IndexOf('=', eq);
- t2 = line.IndexOf("+=", eq);
- if(t1 < 0 && t2 < 0) throw new Exception("Key/Value without an assignment: " + file + "@" + s.ToString());
- if(t1 > 0 && t2 > 0) eq = Math.Min(t1, t2);
- else eq = Math.Max(t1, t2);
- int ti = eq - 1;
- bool unescaped = true;
- while(ti >= 0 && line[eq] == '\\')
- {
- unescaped = !unescaped;
- ti--;
- }
- if(unescaped)
- {
- found = true;
- if(line[eq] == '+')
- {
- s = eq + 2;
- append = true;
- }
- else s = eq + 1;
- }
- else
- {
- if(line[eq] == '+') eq += 2;
- else eq++;
- }
- }
- key = line.Substring(0, eq);
- value = line.Substring(s);
- key = UnEscapeString(key);
- value = UnEscapeString(value);
- return true;
- }
- /// <summary>
- /// Return an array of strings, splitting the listed string into lines.
- /// </summary>
- /// <param name="s">The line to split</param>
- /// <param name="llen">The lenght of the lines, or <0 for no split.</param>
- /// <returns>The split line</returns>
- static public string[] SplitLine(string s, int llen)
- {
- List<string> ret = new List<string>(s.Split(c_newlines, StringSplitOptions.None));
- for(int i = 0; i < ret.Count; i++)
- {
- if(ret[i].Length >= llen)
- {
- string line;
- string rem = ret[i];
- ret.RemoveAt(i);
- while(rem.Length > 0)
- {
- char check = rem[0];
- /* this should be handled in the write section - comments SHOULD NOT
- * have this added
- */
- /*if(check == '_' || char.IsWhiteSpace(check))
- {
- rem = "_"+rem;
- }*/
-
- int c = Math.Min(llen, rem.Length);
- int cut;
- if(c < rem.Length)
- {
- cut = c;
- c = LastWhiteSpace(rem, c);
- if(c < 0) c = cut;
- }
-
- int escape_count=0, tc=c-1;
- check = rem[tc];
- while(tc>=0)
- {
- if(rem[tc]=='\\') escape_count++;
- else break;
- tc--;
- }
- if(check == '_' || char.IsWhiteSpace(check))
- {
- escape_count=escape_count%2+1; //we don't want to break up escapes
- line=rem.Substring(0, c-escape_count);
- rem=rem.Substring(c-escape_count);
- }
- else
- {
- escape_count=escape_count%2; //don't split escape pairs
- line=rem.Substring(0, c-escape_count);
- rem=rem.Substring(c-escape_count);
- }
-
- ret.Insert(i, line);
- i++;
- }
- }
- }
- return ret.ToArray();
- }
- /// <summary>
- /// Escape a string to put in a config file
- /// </summary>
- /// <param name="s">The string to escape</param>
- /// <returns>The escaped string</returns>
- static public string Escape(string s)
- {
- s = s.Replace("\\", "\\\\").Replace("\r\n", "\\n").Replace("\n", "\\n").Replace("\f", "\\f").Replace("\r", "\\n");
- s = s.Replace("\t","\\t").Replace("\v","\\v");
- return s;
- }
- /// <summary>
- /// Find the last whitespace character
- /// </summary>
- /// <param name="s">The string to search</param>
- /// <param name="last">Only accept whitespace at or prior to this position</param>
- /// <returns>Return the position, or -1 if not found</returns>
- static public int LastWhiteSpace(string s, int last)
- {
- do
- {
- if(Char.IsWhiteSpace(s[last])) return last;
- last--;
- }while(last >= 0);
- return -1;
- }
-
- /// <summary>
- /// Try to intelligently determine the encoder needed to turn readbuff into a string.
- /// </summary>
- /// <param name="readbuff">The buffer to read. It may end up being reversed for big endian UTF-32</param>
- /// <param name="enc">This is changed to the encoder indicated by the BOM, if there is an aparant BOM.</param>
- static public void get_decoder(ref byte[] readbuff, ref Encoding enc)
- {
- if(readbuff.Length > 2)
- {
- if(readbuff.Length > 3)
- {
- if(readbuff[0] == 0xEF && readbuff[1] == 0xBB && readbuff[2] == 0xBF)
- {
- enc = Encoding.UTF8;
- return;
- }
- //this in partiuclar has to be before UTF-16,
- //as Little Endian UTF-32 starts with the same
- //first two characters
- if(readbuff.Length > 4)
- {
- if(readbuff.Length % 4 == 0)
- {
- if(readbuff[0] == 0xFF && readbuff[1] == 0xFE && readbuff[2] == 0x00 && readbuff[3] == 0x00)
- {
- enc = Encoding.UTF32;
- return;
- }
- if(readbuff[0] == 0x00 && readbuff[1] == 0x00 && readbuff[2] == 0xFE && readbuff[3] == 0xFF)
- {
- enc = Encoding.UTF32;
- //big endian, we need to flip
- for(int i = 0; i < readbuff.Length; i += 4)
- {
- byte c = readbuff[i];
- readbuff[i] = readbuff[i + 3];
- readbuff[i + 3] = c;
-
- c = readbuff[i + 1];
- readbuff[i + 1] = readbuff[i + 2];
- readbuff[i + 2] = c;
- }
- return;
- }
- }
- if(readbuff[0] == 0x2B && readbuff[1] == 0x2F && readbuff[2] == 0x76 &&
- (readbuff[3] == 0x38 || readbuff[3] == 0x39 || readbuff[3] == 0x2B || readbuff[3] == 0x2F))
- {
- enc = Encoding.UTF7;
- return;
- }
- }
- }
- if(readbuff.Length % 2 == 0)
- {
- if(readbuff[0] == 0xFF && readbuff[1] == 0xFE)
- {
- enc = Encoding.Unicode;
- return;
- }
- if(readbuff[0] == 0xFE && readbuff[1] == 0xFF)
- {
- enc = Encoding.BigEndianUnicode;
- return;
- }
- }
- }
- }
- }
- }