PageRenderTime 47ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/ConfigFileManager/ConfigFile.cs

#
C# | 917 lines | 726 code | 16 blank | 175 comment | 175 complexity | e641af7d44bf5e8cf4967851b9311052 MD5 | raw file
  1. /*
  2. COPYRIGHT NOTICE
  3. Copyright (c) 2011, S James S Stapleton
  4. All rights reserved.
  5. Redistribution and use in source and binary forms, with or without modification,
  6. are permitted provided that the following conditions are met:
  7. * Redistributions of source code must retain the above copyright notice,
  8. this list of conditions and the following disclaimer.
  9. * Redistributions in binary form must reproduce the above copyright notice,
  10. this list of conditions and the following disclaimer in the documentation
  11. and/or other materials provided with the distribution.
  12. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  13. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  14. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  15. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  16. ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  17. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  18. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  19. ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  20. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  21. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  22. */
  23. using System;
  24. using System.Collections.Generic;
  25. using System.Linq;
  26. using System.Text;
  27. using System.IO;
  28. namespace ConfigFileManager
  29. {
  30. public static class ConfigFile
  31. {
  32. static private Encoding c_default_encoding = Encoding.Default /*Encoding.UTF8*/;
  33. /// <summary>
  34. /// Get/set the default encoding used for reading/writing files.
  35. /// Uses the system default initially. Note: switching encodings is not thread safe.
  36. /// </summary>
  37. static public Encoding default_encoding
  38. {
  39. get
  40. {
  41. return c_default_encoding;
  42. }
  43. set
  44. {
  45. c_default_encoding = value;
  46. }
  47. }
  48. /// <summary>
  49. /// Set the ConfigFile tool to use the system default encoding. Note: switching encodings is not thread safe.
  50. /// </summary>
  51. static public void SystemDefaultEncoding()
  52. {
  53. c_default_encoding = Encoding.Default;
  54. }
  55. /// <summary>
  56. /// Honor bom? to determine encoding. Set to false to always use the default/set encoding.
  57. /// </summary>
  58. static public bool honor_bom = true;
  59. static public readonly int c_version_major = 0;
  60. static public readonly int c_version_minor = 4;
  61. static public readonly int c_version_build = 9;
  62. static public readonly string c_version_string = "ConfigFileManager Version " + c_version_major.ToString() +
  63. "." + c_version_minor.ToString() + "." + c_version_build.ToString() + " (alpha)";
  64. static public int c_line_length = 80;
  65. /// <summary>
  66. /// Get/Set the length of lines in Write()'ed files
  67. /// </summary>
  68. static public int line_length
  69. {
  70. get
  71. {
  72. return c_line_length;
  73. }
  74. set
  75. {
  76. c_line_length = value;
  77. }
  78. }
  79. /// <summary>
  80. /// New line characters, for splitting strings
  81. /// </summary>
  82. static public readonly string[] c_newlines = new string[] { "\r\n", "\n", "\r" };
  83. /// <summary>
  84. /// Whitespace, used for acceptable line splits
  85. /// </summary>
  86. static public readonly char[] c_whitespace = new char[] { ' ', '\t', '\v', '(', ')', '[', ']', '{', '}' };
  87. /// <summary>
  88. /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
  89. /// </summary>
  90. /// <param name="fname">The name of the file the entries were read from.</param>
  91. /// <param name="outd">The configuration file is placed into outd.</param>
  92. /// <param name="lines">The file content, split by lines, to parse into outd.</param>
  93. static public void Read(string fname, ref Dictionary<string, string> outd, string[] lines)
  94. {
  95. string key;
  96. string val;
  97. bool append;
  98. int i = 0;
  99. while(GetNextKeyValue(fname, ref i, lines, out append, out key, out val))
  100. {
  101. if(append && outd.ContainsKey(key))
  102. outd[key]+= val;
  103. else
  104. outd[key] = val;
  105. }
  106. }
  107. /// <summary>
  108. /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
  109. /// </summary>
  110. /// <param name="fname">The name of the file the entries were read from. It will be opened and read</param>
  111. /// <param name="outd">The configuration file is placed into outd.</param>
  112. static public void Read(string fname, ref Dictionary<string, string> outd)
  113. {
  114. using(FileStream fp = new FileStream(fname, FileMode.Open, FileAccess.Read))
  115. {
  116. byte[] readbuff = new byte[fp.Length];
  117. int pos = 0;
  118. while(pos < fp.Length)
  119. pos += fp.Read(readbuff, pos, (int)fp.Length-pos);
  120. Encoding enc = c_default_encoding;
  121. //try to honor BOM
  122. if(honor_bom)
  123. {
  124. get_decoder(ref readbuff, ref enc);
  125. }
  126. string buff = enc.GetString(readbuff);
  127. string[] lines = buff.Split(c_newlines, StringSplitOptions.None);
  128. Read(fname, ref outd, lines);
  129. }
  130. }
  131. /// <summary>
  132. /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
  133. /// </summary>
  134. /// <param name="fname">The name of the file the entries were read from.</param>
  135. /// <param name="keyorder">The order of the keys, read from the file, </param>
  136. /// <param name="outd">The configuration file is placed into outd.</param>
  137. /// <param name="lines">The file content, split by lines, to parse into outd.</param>
  138. /// <param name="comments">Comments are stored here. Set passed arg to null to not read comments.</param>
  139. static public void Read(string fname, out string[] keyorder, ref Dictionary<string, string> outd, ref Dictionary<string,string> comments, string[] lines)
  140. {
  141. List<string> keyo = new List<string>();
  142. string key;
  143. string val;
  144. bool append;
  145. string comment;
  146. int i = 0;
  147. while(GetNextKeyValue(fname, ref i, lines, out append, out key, out val, out comment))
  148. {
  149. if(append && outd.ContainsKey(key))
  150. outd[key] += val;
  151. else
  152. outd[key] = val;
  153. if(!keyo.Contains(key)) keyo.Add(key);
  154. if(comments != null)
  155. {
  156. if(append && comments.ContainsKey(key))
  157. comments[key] += comment;
  158. else
  159. comments[key] = comment;
  160. }
  161. }
  162. keyorder = keyo.ToArray<string>();
  163. }
  164. /// <summary>
  165. /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
  166. /// </summary>
  167. /// <param name="fname">The name of the file the entries were read from. It will be opened and read</param>
  168. /// <param name="keyorder">The order of the keys, read from the file, </param>
  169. /// <param name="outd">The configuration file is placed into outd.</param>
  170. /// <param name="comments">Comments are stored here. Set passed arg to null to not read comments.</param>
  171. static public void Read(string fname, out string[] keyorder, ref Dictionary<string, string> outd, ref Dictionary<string,string> comments)
  172. {
  173. List<string> keyo = new List<string>();
  174. using(FileStream fp = new FileStream(fname, FileMode.Open, FileAccess.Read))
  175. {
  176. byte[] readbuff = new byte[fp.Length];
  177. int pos = 0;
  178. while(pos < fp.Length)
  179. pos += fp.Read(readbuff, pos, (int)fp.Length - pos);
  180. Encoding enc = c_default_encoding;
  181. //try to honor BOM
  182. if(honor_bom)
  183. {
  184. get_decoder(ref readbuff, ref enc);
  185. }
  186. string buff = enc.GetString(readbuff);
  187. string[] lines = buff.Split(c_newlines, StringSplitOptions.None);
  188. Read(fname, out keyorder, ref outd, ref comments, lines);
  189. }
  190. }
  191. /// <summary>
  192. /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
  193. /// </summary>
  194. /// <param name="fname">The name of the file the entries were read from.</param>
  195. /// <param name="keyorder">The order of the keys, read from the file, </param>
  196. /// <param name="outd">The configuration file is placed into outd.</param>
  197. /// <param name="lines">The file content, split by lines, to parse into outd.</param>
  198. /// <param name="comments">Comments are stored here. There will be one entry per key. If left null, comments won't be retrieved.</param>
  199. static public void Read(string fname, out IList<string> keyorder, ref Dictionary<string, IList<string>> outd, ref Dictionary<string, IList<string>> comments, string[] lines)
  200. {
  201. outd.Clear();
  202. if(comments != null) comments.Clear();
  203. keyorder = new List<string>();
  204. string key;
  205. string val;
  206. bool append;
  207. string comment;
  208. int i = 0;
  209. while(GetNextKeyValue(fname, ref i, lines, out append, out key, out val, out comment))
  210. {
  211. if(!append || !outd.ContainsKey(key))
  212. outd[key] = new List<string>();
  213. outd[key].Add(val);
  214. if(comments != null)
  215. {
  216. if(comment==null)comment="";
  217. if(!append || !comments.ContainsKey(key))
  218. comments[key] = new List<string>();
  219. comments[key].Add(comment);
  220. }
  221. ((List<string>)keyorder).Add(key);
  222. }
  223. }
  224. /// <summary>
  225. /// Read the config file. Lines starting with a non-null '#' are ignored. Blank lines are ignored.
  226. /// </summary>
  227. /// <param name="fname">The name of the file the entries were read from. It will be opened and read</param>
  228. /// <param name="keyorder">The order of the keys, read from the file, </param>
  229. /// <param name="outd">The configuration file is placed into outd.</param>
  230. /// <param name="comments">Comments are stored here. Set passed arg to null to not read comments.</param>
  231. static public void Read(string fname, out IList<string> keyorder, ref Dictionary<string, IList<string>> outd, ref Dictionary<string, IList<string>> comments)
  232. {
  233. List<string> keyo = new List<string>();
  234. using(FileStream fp = new FileStream(fname, FileMode.Open, FileAccess.Read))
  235. {
  236. byte[] readbuff = new byte[fp.Length];
  237. int pos = 0;
  238. while(pos < fp.Length)
  239. pos += fp.Read(readbuff, pos, (int)fp.Length - pos);
  240. Encoding enc = c_default_encoding;
  241. //try to honor BOM
  242. if(honor_bom)
  243. {
  244. get_decoder(ref readbuff, ref enc);
  245. }
  246. string buff = enc.GetString(readbuff);
  247. string[] lines = buff.Split(c_newlines, StringSplitOptions.None);
  248. Read(fname, out keyorder, ref outd, ref comments, lines);
  249. }
  250. }
  251. /// <summary>
  252. /// Writes a configuration file. Any carrage return, newline, and carrage return + newlines found in keys or
  253. /// values will be replaced by newlines when they are escaped. I.E. The following C# string:
  254. /// "\n \r \r\n" will be written to file as this C# string "\\n \\n \\n", or if opened a text editor, this exact
  255. /// string: `\n \n \n`.
  256. /// </summary>
  257. /// <param name="fname">The name of the output file</param>
  258. /// <param name="pairs">The configuration to write</param>
  259. /// <param name="comments">Comments. Each comment is given a key, the comment is written before that value.</param>
  260. /// <param name="order">Write keys in this order. Keys not in this list will appear later, in arbitrary order</param>
  261. static public void Write(string fname, Dictionary<string, string> pairs, Dictionary<string, string> comments, string[] order)
  262. {
  263. //step one, generate the output to write.
  264. StringBuilder output = new StringBuilder();
  265. string[] entry;
  266. if(order != null && order.Length > 0)
  267. {
  268. for(int i = 0; i < order.Length; i++)
  269. {
  270. entry = GetEntry(order[i], pairs, comments);
  271. foreach(string s in entry)
  272. output.Append(s);
  273. output.Append(Environment.NewLine);
  274. }
  275. }
  276. foreach(string s in pairs.Keys)
  277. {
  278. if(order.Contains<string>(s)) continue;
  279. entry = GetEntry(s, pairs, comments);
  280. foreach(string st in entry)
  281. output.Append(st);
  282. output.Append(Environment.NewLine);
  283. }
  284. byte[] outp = c_default_encoding.GetBytes(output.ToString());
  285. output = null;
  286. using(FileStream fp = new FileStream(fname, FileMode.Create, FileAccess.Write))
  287. {
  288. fp.Write(outp, 0, outp.Length);
  289. }
  290. }
  291. /// <summary>
  292. /// Writes a configuration file. Any carrage return, newline, and carrage return + newlines found in keys or
  293. /// values will be replaced by newlines when they are escaped. I.E. The following C# string:
  294. /// "\n \r \r\n" will be written to file as this C# string "\\n \\n \\n", or if opened a text editor, this exact
  295. /// string: `\n \n \n`.
  296. /// </summary>
  297. /// <param name="fname">The name of the output file</param>
  298. /// <param name="pairs">The configuration to write</param>
  299. /// <param name="comments">Comments. Each comment is given a key, the comment is written before that value.</param>
  300. /// <param name="order">Write keys in this order. Keys not in this list will appear later, in arbitrary order</param>
  301. static public void Write(string fname, Dictionary<string, IList<string>> pairs, Dictionary<string, IList<string>> comments, IList<string> order)
  302. {
  303. //step one, generate the output to write.
  304. StringBuilder output = new StringBuilder();
  305. Dictionary<string, int> saved_to = new Dictionary<string, int>(pairs.Count);
  306. foreach(string key in pairs.Keys)
  307. {
  308. saved_to[key] = 0;
  309. }
  310. string[] entry;
  311. if(order != null && order.Count > 0)
  312. {
  313. for(int i = 0; i < order.Count; i++)
  314. {
  315. string key = order[i];
  316. int p = saved_to[key];
  317. entry = GetEntry(key, p, pairs, comments);
  318. foreach(string s in entry)
  319. output.Append(s);
  320. output.Append(Environment.NewLine);
  321. saved_to[key]++;
  322. }
  323. }
  324. foreach(string s in pairs.Keys)
  325. {
  326. int p = saved_to[s];
  327. while(p < pairs[s].Count)
  328. {
  329. entry = GetEntry(s, p, pairs, comments);
  330. foreach(string st in entry)
  331. output.Append(st);
  332. output.Append(Environment.NewLine);
  333. p++;
  334. }
  335. }
  336. byte[] outp = c_default_encoding.GetBytes(output.ToString());
  337. output = null;
  338. using(FileStream fp = new FileStream(fname, FileMode.Create, FileAccess.Write))
  339. {
  340. fp.Write(outp, 0, outp.Length);
  341. }
  342. }
  343. /// <summary>
  344. /// Get the output config lines for the config file.
  345. /// Note that comments are not escaped, but values and keys are!
  346. /// </summary>
  347. /// <param name="key">The config file key for the entry of interest</param>
  348. /// <param name="pairs">The config key/value file values</param>
  349. /// <param name="comments">The comments for some keys of interest</param>
  350. /// <returns>The lines for the specified entry in a config file</returns>
  351. static private string[] GetEntry(string key, Dictionary<string, string> pairs, Dictionary<string, string> comments)
  352. {
  353. List<string> ret = new List<string>();
  354. string val = null;
  355. string[] vals;
  356. if(comments != null && comments.TryGetValue(key, out val) && val != null)
  357. {
  358. if(c_line_length > 10)
  359. {
  360. vals = SplitLine(val, c_line_length - 2);
  361. foreach(string v in vals)
  362. if(v != null)
  363. ret.Add(string.Format("# {0}{1}", v, Environment.NewLine));
  364. }
  365. else
  366. ret.Add(string.Format("# {0}{1}", val, Environment.NewLine));
  367. }
  368. val=pairs[key];
  369. if(val==null)
  370. return new string[] { "" };
  371. val = Escape(key) + "=" + Escape(val);
  372. vals = SplitLine(val, c_line_length);
  373. int i=0;
  374. foreach(string v in vals)
  375. {
  376. i++;
  377. string va = v;
  378. char tc=va[0];
  379. if(char.IsWhiteSpace(tc) || tc == '#' || tc == '_') va = "_" + va;
  380. tc = va[va.Length-1];
  381. if(i < vals.Length) va += '\\';
  382. else if(char.IsWhiteSpace(tc) || tc == '_') va += "_";
  383. ret.Add(string.Format("{0}{1}", va, Environment.NewLine));
  384. }
  385. return ret.ToArray();
  386. }
  387. /// <summary>
  388. /// Get the output config lines for the config file. Each value can have multiple assigned strings.
  389. /// Note that comments are not escaped, but values and keys are!
  390. /// </summary>
  391. /// <param name="key">The config file key for the entry of interest</param>
  392. /// <param name="pos">The position of the desired string within pairs/comments.</param>
  393. /// <param name="pairs">The config key/value file values</param>
  394. /// <param name="comments">The comments for some keys of interest.</param>
  395. /// <returns>The lines for the specified entry in a config file. Any given value in comments must have the
  396. /// same count as the value assigned to a similar key in pairs.</returns>
  397. static private string[] GetEntry(string key, int pos, Dictionary<string, IList<string>> pairs, Dictionary<string, IList<string>> comments)
  398. {
  399. List<string> ret = new List<string>();
  400. IList<string> val = null;
  401. string va = pairs[key][pos];
  402. if(va==null)
  403. {
  404. return new string[] { "" };
  405. }
  406. string[] vals;
  407. if(comments != null && comments.TryGetValue(key, out val) && val != null)
  408. {
  409. if(val[pos].Length > 0)
  410. {
  411. if(c_line_length > 10)
  412. {
  413. vals = SplitLine(val[pos], c_line_length - 2);
  414. foreach(string v in vals)
  415. if(v!=null)
  416. ret.Add(string.Format("# {0}{1}", v, Environment.NewLine));
  417. }
  418. else
  419. ret.Add(string.Format("# {0}{1}", val[pos], Environment.NewLine));
  420. }
  421. }
  422. string eq;
  423. if(pos == 0) eq = "=";
  424. else eq = "+=";
  425. va = Escape(key) + eq + Escape(va);
  426. vals = SplitLine(va, c_line_length);
  427. int i = 0;
  428. foreach(string v in vals)
  429. {
  430. i++;
  431. va = v;
  432. char tc=va[0];
  433. if(char.IsWhiteSpace(tc)||tc=='#'||tc=='_') va="_"+va;
  434. tc=va[va.Length-1];
  435. if(i < vals.Length) va += "\\";
  436. else if(char.IsWhiteSpace(tc) || tc == '_') va += "_";
  437. ret.Add(string.Format("{0}{1}", va, Environment.NewLine));
  438. }
  439. return ret.ToArray();
  440. }
  441. /// <summary>
  442. /// A utility function for helping with escaped strings - counts the number fo slashes prior to an index.
  443. /// </summary>
  444. /// <param name="s">The string of interest</param>
  445. /// <param name="index">The index to start counting from (actually, index-1).</param>
  446. /// <returns></returns>
  447. static public int CountPreSlashes(string s, int index)
  448. {
  449. int j = 0;
  450. for(j = index - 1; j >= 0 && s[j] == '/'; j--) ;
  451. return index - j - 1;
  452. }
  453. /// <summary>
  454. /// unescapes strings
  455. /// \n -> newline
  456. /// \r -> carrage return
  457. /// \f -> form feed
  458. /// \\ -> slash
  459. /// \t -> horiz. tab
  460. /// \v -> vert. tab
  461. /// \[NEWLINE] -> ignored, newline removed
  462. /// \0x## -> ASCII ##
  463. /// \u#### -> Unicode ####
  464. /// </summary>
  465. /// <param name="s">The string to unescape</param>
  466. /// <returns>The unescaped string</returns>
  467. static public string UnEscapeString(string s)
  468. {
  469. List<object> ret = new List<object>();
  470. int st = 0;
  471. for(int i = 0; i < s.Length; i++)
  472. {
  473. if(s[i] == '\\')
  474. {
  475. if(i != st)
  476. {
  477. ret.Add(s.Substring(st, i-st));
  478. }
  479. i++;
  480. if(i == s.Length)
  481. {
  482. throw new Exception("UnEscaping string, non-escaping '\\' at End of String");
  483. }
  484. char c = s[i];
  485. switch(c)
  486. {
  487. case 'n':
  488. ret.Add(Environment.NewLine);
  489. break;
  490. case 'r':
  491. if(s.Length >= i + 2 && s[i + 1] == '\\' && s[i + 2] == 'n')
  492. {
  493. i+=2;
  494. ret.Add(Environment.NewLine);
  495. }
  496. else
  497. ret.Add(Environment.NewLine);
  498. break;
  499. case '\\':
  500. ret.Add("\\");
  501. break;
  502. case 'f':
  503. ret.Add("\f");
  504. break;
  505. case 't':
  506. ret.Add("\t");
  507. break;
  508. case 'v':
  509. ret.Add("\v");
  510. break;
  511. case '0':
  512. if(i + 2 < s.Length && (s[i] == 'x' || s[i] == 'X'))
  513. {
  514. ret.Add((byte)Convert.ToByte(s.Substring(i + 1, i + 3)));
  515. i += 2;
  516. break;
  517. }
  518. throw new Exception("Error in escaping string, '\\0' must be followed by 'x##', where ## is two numbers.");
  519. case 'u':
  520. if(i + 4 < s.Length && (s[i] == 'u' || s[i] == 'U'))
  521. {
  522. ret.Add((byte)Convert.ToByte(s.Substring(i + 1, i + 3)));
  523. ret.Add((byte)Convert.ToByte(s.Substring(i + 3, i + 5)));
  524. i += 4;
  525. break;
  526. }
  527. throw new Exception("Error in escaping string '\\u' must be followed by '####', where #### is for numbers.");
  528. default:
  529. ret.Add(s[i]);
  530. break;
  531. }
  532. st = i + 1;
  533. }
  534. }
  535. if(st < s.Length)
  536. {
  537. ret.Add(s.Substring(st));
  538. }
  539. //merge the string
  540. if(ret.Count == 1 && ret[0].GetType() == typeof(string))
  541. return (string)ret[0];
  542. StringBuilder sb = new StringBuilder();
  543. for(int i = 0; i < ret.Count; i++)
  544. {
  545. object o = ret[i];
  546. if(o.GetType() == typeof(string))
  547. {
  548. sb.Append((string)o);
  549. continue;
  550. }
  551. int start = i;
  552. while(i < ret.Count && ret[i].GetType() == typeof(byte))
  553. {
  554. i++;
  555. }
  556. byte[] buff = new byte[i-start];
  557. for(int j = st; j < i; j++)
  558. buff[j-st] = (byte)ret[j];
  559. sb.Append(Encoding.UTF8.GetString(buff));
  560. }
  561. return sb.ToString();
  562. }
  563. /// <summary>
  564. /// This gets the next key/value from the lines loaded from a config file.
  565. /// </summary>
  566. /// <param name="file">The name of the file - for printing errors.</param>
  567. /// <param name="i">The line to start looking, in the file, set to the line past the last line loaded.</param>
  568. /// <param name="lines">The lines in the file.</param>
  569. /// <param name="append">This is set to true, if the the key/value is to be appended.</param>
  570. /// <param name="key">This is set to the key found.</param>
  571. /// <param name="value">This is set to the value found.</param>
  572. /// <returns>True if a key/value is found, otherwise false.</returns>
  573. static public bool GetNextKeyValue(string file, ref int i, string[] lines, out bool append, out string key, out string value)
  574. {
  575. int s = i;
  576. key = null;
  577. value = null;
  578. append = false;
  579. string line = "";
  580. //get the full line
  581. while(true)
  582. {
  583. if(i == lines.Length)
  584. {
  585. if(line == "") return false;
  586. else break;
  587. }
  588. string txt = lines[i];
  589. txt = lines[i];
  590. txt = txt.Trim();
  591. i++;
  592. if(txt.Length == 0) continue; //ignore blank lines.
  593. if(txt.Substring(0, 1) == "#") continue; //skip comment lines
  594. if(txt.Substring(0, 1) == "_") txt = txt.Substring(1);
  595. if(txt.Substring(txt.Length - 1) == "_") txt = txt.Substring(0, txt.Length - 1);
  596. line += txt;
  597. if(line.Substring(line.Length - 1) == "\\") line = line.Substring(0, line.Length - 1);
  598. else break;
  599. }
  600. //we have the line, find the assignment
  601. bool found = false;
  602. int eq = 0;
  603. int t1 = 0;
  604. int t2 = 0;
  605. while(!found)
  606. {
  607. t1 = line.IndexOf('=', eq);
  608. t2 = line.IndexOf("+=", eq);
  609. if(t1 < 0 && t2 < 0) throw new Exception("Key/Value without an assignment: " + file + "@" + s.ToString());
  610. if(t1 > 0 && t2 > 0) eq = Math.Min(t1, t2);
  611. else eq = Math.Max(t1, t2);
  612. int ti = eq - 1;
  613. bool unescaped = true;
  614. while(ti >= 0 && line[eq] == '\\')
  615. {
  616. unescaped = !unescaped;
  617. ti--;
  618. }
  619. if(unescaped)
  620. {
  621. found = true;
  622. if(line[eq] == '+')
  623. {
  624. s = eq + 2;
  625. append = true;
  626. }
  627. else s = eq + 1;
  628. }
  629. else
  630. {
  631. if(line[eq] == '+') eq += 2;
  632. else eq++;
  633. }
  634. }
  635. key = line.Substring(0, eq);
  636. value = line.Substring(s);
  637. key = UnEscapeString(key);
  638. value = UnEscapeString(value);
  639. return true;
  640. }
  641. /// <summary>
  642. /// This gets the next key/value from the lines loaded from a config file.
  643. /// </summary>
  644. /// <param name="file">The name of the file - for printing errors.</param>
  645. /// <param name="i">The line to start looking, in the file, set to the line past the last line loaded.</param>
  646. /// <param name="lines">The lines in the file.</param>
  647. /// <param name="append">This is set to true, if the the key/value is to be appended.</param>
  648. /// <param name="key">This is set to the key found.</param>
  649. /// <param name="value">This is set to the value found.</param>
  650. /// <returns>True if a key/value is found, otherwise false.</returns>
  651. static public bool GetNextKeyValue(string file, ref int i, string[] lines, out bool append, out string key, out string value, out string comment)
  652. {
  653. int s = i;
  654. key = null;
  655. value = null;
  656. comment = null;
  657. append = false;
  658. string line = "";
  659. //get the full line
  660. bool cfirst = true;
  661. while(true)
  662. {
  663. if(i == lines.Length)
  664. {
  665. if(line == "") return false;
  666. else break;
  667. }
  668. string txt = lines[i];
  669. txt = lines[i];
  670. txt = txt.Trim();
  671. i++;
  672. if(txt.Length == 0) continue; //ignore blank lines.
  673. if(txt.Substring(0, 1) == "#") // read comments
  674. {
  675. if(comment == null)
  676. {
  677. comment = "";
  678. }
  679. string c = txt.Substring(1);
  680. if(c.Length > 0 && c[0] == ' ') c = c.Substring(1);
  681. //check for/handle multi-newlines
  682. if(c.Length == 0)
  683. {
  684. if(comment.Length > 0 && comment[comment.Length - 1] != '\n' && comment[comment.Length - 1] != '\r')
  685. comment += Environment.NewLine;
  686. comment += Environment.NewLine;
  687. }
  688. //insert a a space in a comment line break between two non-whitespace, non-dash, non-underscore
  689. char t;
  690. if(c.Length > 0 && !char.IsWhiteSpace(t=c[0]) && t != '-' && t != '_' &&
  691. (comment.Length > 0 && !char.IsWhiteSpace(t=comment[comment.Length - 1]) && t!='-' && t != '_')) comment += " ";
  692. comment += c;
  693. continue;
  694. }
  695. if(txt.Substring(0, 1) == "_") txt = txt.Substring(1);
  696. if(txt.Substring(txt.Length - 1) == "_") txt = txt.Substring(0, txt.Length - 1);
  697. line += txt;
  698. if(line.Substring(line.Length - 1) == "\\") line = line.Substring(0, line.Length - 1);
  699. else break;
  700. }
  701. //we have the line, find the assignment
  702. bool found = false;
  703. int eq = 0;
  704. int t1 = 0;
  705. int t2 = 0;
  706. while(!found)
  707. {
  708. t1 = line.IndexOf('=', eq);
  709. t2 = line.IndexOf("+=", eq);
  710. if(t1 < 0 && t2 < 0) throw new Exception("Key/Value without an assignment: " + file + "@" + s.ToString());
  711. if(t1 > 0 && t2 > 0) eq = Math.Min(t1, t2);
  712. else eq = Math.Max(t1, t2);
  713. int ti = eq - 1;
  714. bool unescaped = true;
  715. while(ti >= 0 && line[eq] == '\\')
  716. {
  717. unescaped = !unescaped;
  718. ti--;
  719. }
  720. if(unescaped)
  721. {
  722. found = true;
  723. if(line[eq] == '+')
  724. {
  725. s = eq + 2;
  726. append = true;
  727. }
  728. else s = eq + 1;
  729. }
  730. else
  731. {
  732. if(line[eq] == '+') eq += 2;
  733. else eq++;
  734. }
  735. }
  736. key = line.Substring(0, eq);
  737. value = line.Substring(s);
  738. key = UnEscapeString(key);
  739. value = UnEscapeString(value);
  740. return true;
  741. }
  742. /// <summary>
  743. /// Return an array of strings, splitting the listed string into lines.
  744. /// </summary>
  745. /// <param name="s">The line to split</param>
  746. /// <param name="llen">The lenght of the lines, or &lt;0 for no split.</param>
  747. /// <returns>The split line</returns>
  748. static public string[] SplitLine(string s, int llen)
  749. {
  750. List<string> ret = new List<string>(s.Split(c_newlines, StringSplitOptions.None));
  751. for(int i = 0; i < ret.Count; i++)
  752. {
  753. if(ret[i].Length >= llen)
  754. {
  755. string line;
  756. string rem = ret[i];
  757. ret.RemoveAt(i);
  758. while(rem.Length > 0)
  759. {
  760. char check = rem[0];
  761. /* this should be handled in the write section - comments SHOULD NOT
  762. * have this added
  763. */
  764. /*if(check == '_' || char.IsWhiteSpace(check))
  765. {
  766. rem = "_"+rem;
  767. }*/
  768. int c = Math.Min(llen, rem.Length);
  769. int cut;
  770. if(c < rem.Length)
  771. {
  772. cut = c;
  773. c = LastWhiteSpace(rem, c);
  774. if(c < 0) c = cut;
  775. }
  776. int escape_count=0, tc=c-1;
  777. check = rem[tc];
  778. while(tc>=0)
  779. {
  780. if(rem[tc]=='\\') escape_count++;
  781. else break;
  782. tc--;
  783. }
  784. if(check == '_' || char.IsWhiteSpace(check))
  785. {
  786. escape_count=escape_count%2+1; //we don't want to break up escapes
  787. line=rem.Substring(0, c-escape_count);
  788. rem=rem.Substring(c-escape_count);
  789. }
  790. else
  791. {
  792. escape_count=escape_count%2; //don't split escape pairs
  793. line=rem.Substring(0, c-escape_count);
  794. rem=rem.Substring(c-escape_count);
  795. }
  796. ret.Insert(i, line);
  797. i++;
  798. }
  799. }
  800. }
  801. return ret.ToArray();
  802. }
  803. /// <summary>
  804. /// Escape a string to put in a config file
  805. /// </summary>
  806. /// <param name="s">The string to escape</param>
  807. /// <returns>The escaped string</returns>
  808. static public string Escape(string s)
  809. {
  810. s = s.Replace("\\", "\\\\").Replace("\r\n", "\\n").Replace("\n", "\\n").Replace("\f", "\\f").Replace("\r", "\\n");
  811. s = s.Replace("\t","\\t").Replace("\v","\\v");
  812. return s;
  813. }
  814. /// <summary>
  815. /// Find the last whitespace character
  816. /// </summary>
  817. /// <param name="s">The string to search</param>
  818. /// <param name="last">Only accept whitespace at or prior to this position</param>
  819. /// <returns>Return the position, or -1 if not found</returns>
  820. static public int LastWhiteSpace(string s, int last)
  821. {
  822. do
  823. {
  824. if(Char.IsWhiteSpace(s[last])) return last;
  825. last--;
  826. }while(last >= 0);
  827. return -1;
  828. }
  829. /// <summary>
  830. /// Try to intelligently determine the encoder needed to turn readbuff into a string.
  831. /// </summary>
  832. /// <param name="readbuff">The buffer to read. It may end up being reversed for big endian UTF-32</param>
  833. /// <param name="enc">This is changed to the encoder indicated by the BOM, if there is an aparant BOM.</param>
  834. static public void get_decoder(ref byte[] readbuff, ref Encoding enc)
  835. {
  836. if(readbuff.Length > 2)
  837. {
  838. if(readbuff.Length > 3)
  839. {
  840. if(readbuff[0] == 0xEF && readbuff[1] == 0xBB && readbuff[2] == 0xBF)
  841. {
  842. enc = Encoding.UTF8;
  843. return;
  844. }
  845. //this in partiuclar has to be before UTF-16,
  846. //as Little Endian UTF-32 starts with the same
  847. //first two characters
  848. if(readbuff.Length > 4)
  849. {
  850. if(readbuff.Length % 4 == 0)
  851. {
  852. if(readbuff[0] == 0xFF && readbuff[1] == 0xFE && readbuff[2] == 0x00 && readbuff[3] == 0x00)
  853. {
  854. enc = Encoding.UTF32;
  855. return;
  856. }
  857. if(readbuff[0] == 0x00 && readbuff[1] == 0x00 && readbuff[2] == 0xFE && readbuff[3] == 0xFF)
  858. {
  859. enc = Encoding.UTF32;
  860. //big endian, we need to flip
  861. for(int i = 0; i < readbuff.Length; i += 4)
  862. {
  863. byte c = readbuff[i];
  864. readbuff[i] = readbuff[i + 3];
  865. readbuff[i + 3] = c;
  866. c = readbuff[i + 1];
  867. readbuff[i + 1] = readbuff[i + 2];
  868. readbuff[i + 2] = c;
  869. }
  870. return;
  871. }
  872. }
  873. if(readbuff[0] == 0x2B && readbuff[1] == 0x2F && readbuff[2] == 0x76 &&
  874. (readbuff[3] == 0x38 || readbuff[3] == 0x39 || readbuff[3] == 0x2B || readbuff[3] == 0x2F))
  875. {
  876. enc = Encoding.UTF7;
  877. return;
  878. }
  879. }
  880. }
  881. if(readbuff.Length % 2 == 0)
  882. {
  883. if(readbuff[0] == 0xFF && readbuff[1] == 0xFE)
  884. {
  885. enc = Encoding.Unicode;
  886. return;
  887. }
  888. if(readbuff[0] == 0xFE && readbuff[1] == 0xFF)
  889. {
  890. enc = Encoding.BigEndianUnicode;
  891. return;
  892. }
  893. }
  894. }
  895. }
  896. }
  897. }