Korero – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 ///////////////////////////////////////////////////////////////////////////
2 // Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 //
3 // Please see: http://www.gnu.org/licenses/gpl.html for legal details, //
4 // rights of fair usage, the disclaimer and warranty conditions. //
5 ///////////////////////////////////////////////////////////////////////////
6  
7 using System.Collections;
8 using System.Collections.Generic;
9 using System.Linq;
10 using System.Text;
11  
12 namespace Korero.Serialization
13 {
14 public class CSV : IReadOnlyCollection<string>
15 {
16 #region Static Fields and Constants
17  
18 /// <summary>
19 /// Special CSV characters.
20 /// </summary>
21 private static readonly char[] CsvEscapeCharacters = {'"', ' ', ',', '\r', '\n'};
22  
23 #endregion
24  
25 #region Public Enums, Properties and Fields
26  
27 public int Length => Store.Count;
28  
29 #endregion
30  
31 #region Private Delegates, Events, Enums, Properties, Indexers and Fields
32  
33 protected List<string> Store = new List<string>();
34  
35 #endregion
36  
37 #region Constructors, Destructors and Finalizers
38  
39 public CSV(string csv)
40 {
41 Store = FromStringCSV(csv)
42 .ToList();
43 }
44  
45 public CSV(IEnumerable<string> csv) : this(ToStringCSV(csv))
46 {
47 }
48  
49 public CSV()
50 {
51 }
52  
53 public CSV(IDictionary<string, string> dictionary) : this(FromDictionary(dictionary))
54 {
55 }
56  
57 public CSV(ISet<string> set) : this(set.ToList())
58 {
59 }
60  
61 #endregion
62  
63 #region Interface
64  
65 public IEnumerator<string> GetEnumerator()
66 {
67 return Store.GetEnumerator();
68 }
69  
70 IEnumerator IEnumerable.GetEnumerator()
71 {
72 return ((IEnumerable) Store).GetEnumerator();
73 }
74  
75 public int Count => Store.Count;
76  
77 #endregion
78  
79 #region Public Overrides
80  
81 public override string ToString()
82 {
83 return ToStringCSV(Store);
84 }
85  
86 #endregion
87  
88 #region Operators
89  
90 public static implicit operator string(CSV csv)
91 {
92 return ToStringCSV(csv);
93 }
94  
95 public static implicit operator CSV(string @string)
96 {
97 return new CSV(@string);
98 }
99  
100 public static implicit operator CSV(Dictionary<string, string> dictionary)
101 {
102 return new CSV(FromDictionary(dictionary));
103 }
104  
105 public static implicit operator string[](CSV csv)
106 {
107 return csv.ToArray();
108 }
109  
110 public static implicit operator CSV(string[] array)
111 {
112 return new CSV(array);
113 }
114  
115 #endregion
116  
117 #region Public Methods
118  
119 public string[] ToArray()
120 {
121 return Store.ToArray();
122 }
123  
124 #endregion
125  
126 #region Private Methods
127  
128 ///////////////////////////////////////////////////////////////////////////
129 // Copyright (C) 2016 Wizardry and Steamworks - License: GNU GPLv3 //
130 ///////////////////////////////////////////////////////////////////////////
131 /// <summary>
132 /// Converts a dictionary of strings to a comma-separated values string.
133 /// </summary>
134 /// <returns>a comma-separated list of values</returns>
135 /// <remarks>compliant with RFC 4180</remarks>
136 internal static string FromDictionary<TK, TV>(IDictionary<TK, TV> input)
137 {
138 return string.Join(",",
139 input.Keys.Select(o => o.ToString())
140 .Zip(input.Values.Select(o => o.ToString()),
141 (o, p) =>
142 string.Join(",",
143 o.Replace("\"", "\"\"")
144 .IndexOfAny(CsvEscapeCharacters) ==
145 -1
146 ? o
147 : $"\"{o}\"",
148 p.Replace("\"", "\"\"")
149 .IndexOfAny(CsvEscapeCharacters) ==
150 -1
151 ? p
152 : $"\"{p}\"")));
153 }
154  
155 ///////////////////////////////////////////////////////////////////////////
156 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
157 ///////////////////////////////////////////////////////////////////////////
158 /// <summary>
159 /// Converts a list of strings to a comma-separated values string.
160 /// </summary>
161 /// <returns>a comma-separated list of values</returns>
162 /// <remarks>compliant with RFC 4180</remarks>
163 protected static string ToStringCSV(IEnumerable<string> csv)
164 {
165 var list = csv.ToList();
166  
167 /*
168 * This is How the Cookie Crumbles (yes, it follows RFC 4180 as intended
169 * but not as designed):
170 *
171 * The CSV escape symbol is the double-quote and, as per RFC 4180, it
172 * the double-quote is used to escape cells by surrounding cells that
173 * contain line-breaks, double quotes and commas.
174 *
175 * The purpose for escaping CSV cells conforming with RFC 4180:
176 * * cells with double-quotes - general protection from clashes
177 * with CSV cell escape symbols but since there is only one single
178 * cell, there are no clashes possible since no other cells precede
179 * or follow.
180 * * cells with commas - the comma is used as a delimiter between
181 * cells, such that if a comma is part of a cell's contents, the
182 * cell must be escaped but since if there is only one single cell,
183 * then there is no reason to escape the cell if it contains a comma.
184 *
185 */
186 if (list.Count == 1)
187 {
188 return list.Single();
189 }
190  
191 return string.Join(",",
192 list
193 .Select(o => string.IsNullOrEmpty(o) ? string.Empty : o.Replace("\"", "\"\""))
194 .Select(o => o.IndexOfAny(CsvEscapeCharacters) == -1 ? o : $"\"{o}\""));
195 }
196  
197 ///////////////////////////////////////////////////////////////////////////
198 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
199 ///////////////////////////////////////////////////////////////////////////
200 /// <summary>
201 /// Converts a comma-separated list of values to a list of strings.
202 /// </summary>
203 /// <param name="csv">a comma-separated list of values</param>
204 /// <returns>a list of strings</returns>
205 /// <remarks>compliant with RFC 4180</remarks>
206 protected static IEnumerable<string> FromStringCSV(string csv)
207 {
208 if (string.IsNullOrEmpty(csv))
209 {
210 yield break;
211 }
212  
213 var s = new Stack<char>();
214 var m = new StringBuilder();
215  
216 for (var i = 0; i < csv.Length; ++i)
217 {
218 switch (csv[i])
219 {
220 case ',':
221  
222 if (!s.Any() ||
223 !s.Peek()
224 .Equals('"'))
225 {
226 yield return m.ToString();
227  
228 m = new StringBuilder();
229  
230 continue;
231 }
232  
233 m.Append(csv[i]);
234  
235 continue;
236  
237 case '"':
238  
239 if (i + 1 < csv.Length &&
240 csv[i]
241 .Equals(csv[i + 1]))
242 {
243 m.Append(csv[i]);
244 ++i;
245  
246 continue;
247 }
248  
249 if (!s.Any() ||
250 !s.Peek()
251 .Equals(csv[i]))
252 {
253 s.Push(csv[i]);
254  
255 continue;
256 }
257  
258 s.Pop();
259  
260 continue;
261 }
262  
263 m.Append(csv[i]);
264 }
265  
266 yield return m.ToString();
267 }
268  
269 #endregion
270 }
271 }