QuickImage – 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 QuickImage.Utilities.Serialization.Comma_Separated_Values
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 private readonly List<string> _store;
34  
35 #endregion
36  
37 #region Constructors, Destructors and Finalizers
38  
39 public Csv(string csv) => _store = FromStringCsv(csv)
40 .ToList();
41  
42 public Csv(IEnumerable<string> csv) : this(ToStringCsv(csv)) { }
43  
44 public Csv() => _store = new List<string>();
45  
46 public Csv(IDictionary<string, string> dictionary) : this(FromDictionary(dictionary)) { }
47  
48 public Csv(ISet<string> set) : this(set.ToList()) { }
49  
50 #endregion
51  
52 #region Interface
53  
54 public IEnumerator<string> GetEnumerator() => _store.GetEnumerator();
55  
56 IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_store).GetEnumerator();
57  
58 public int Count => _store.Count;
59  
60 #endregion
61  
62 #region Public Overrides
63  
64 public override string ToString() => ToStringCsv(_store);
65  
66 #endregion
67  
68 #region Operators
69  
70 public static implicit operator string(Csv csv) => ToStringCsv(csv);
71  
72 public static implicit operator Csv(string @string) => new Csv(@string);
73  
74 public static implicit operator Csv(Dictionary<string, string> dictionary) =>
75 new Csv(FromDictionary(dictionary));
76  
77 public static implicit operator string[](Csv csv) => csv.ToArray();
78  
79 public static implicit operator Csv(string[] array) => new Csv(array);
80  
81 #endregion
82  
83 #region Public Methods
84  
85 public string[] ToArray() => _store.ToArray();
86  
87 #endregion
88  
89 #region Private Methods
90  
91 ///////////////////////////////////////////////////////////////////////////
92 // Copyright (C) 2016 Wizardry and Steamworks - License: GNU GPLv3 //
93 ///////////////////////////////////////////////////////////////////////////
94 /// <summary>
95 /// Converts a dictionary of strings to a comma-separated values string.
96 /// </summary>
97 /// <returns>a comma-separated list of values</returns>
98 /// <remarks>compliant with RFC 4180</remarks>
99 private static string FromDictionary<TK, TV>(IDictionary<TK, TV> input)
100 {
101 return string.Join(",",
102 input.Keys.Select(o => o.ToString())
103 .Zip(input.Values.Select(o => o.ToString()),
104 (o, p) =>
105 string.Join(",",
106 o.Replace("\"", "\"\"")
107 .IndexOfAny(CsvEscapeCharacters) ==
108 -1 ?
109 o :
110 $"\"{o}\"",
111 p.Replace("\"", "\"\"")
112 .IndexOfAny(CsvEscapeCharacters) ==
113 -1 ?
114 p :
115 $"\"{p}\"")));
116 }
117  
118 ///////////////////////////////////////////////////////////////////////////
119 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
120 ///////////////////////////////////////////////////////////////////////////
121 /// <summary>
122 /// Converts a list of strings to a comma-separated values string.
123 /// </summary>
124 /// <returns>a comma-separated list of values</returns>
125 /// <remarks>compliant with RFC 4180</remarks>
126 private static string ToStringCsv(IEnumerable<string> csv)
127 {
128 var list = csv.ToList();
129  
130 /*
131 * This is How the Cookie Crumbles (yes, it follows RFC 4180 as intended
132 * but not as designed):
133 *
134 * The CSV escape symbol is the double-quote and, as per RFC 4180, it
135 * the double-quote is used to escape cells by surrounding cells that
136 * contain line-breaks, double quotes and commas.
137 *
138 * The purpose for escaping CSV cells conforming with RFC 4180:
139 * * cells with double-quotes - general protection from clashes
140 * with CSV cell escape symbols but since there is only one single
141 * cell, there are no clashes possible since no other cells precede
142 * or follow.
143 * * cells with commas - the comma is used as a delimiter between
144 * cells, such that if a comma is part of a cell's contents, the
145 * cell must be escaped but since if there is only one single cell,
146 * then there is no reason to escape the cell if it contains a comma.
147 *
148 */
149 if (list.Count == 1)
150 {
151 return list.Single();
152 }
153  
154 return string.Join(",",
155 list
156 .Select(o => string.IsNullOrEmpty(o) ? string.Empty : o.Replace("\"", "\"\""))
157 .Select(o => o.IndexOfAny(CsvEscapeCharacters) == -1 ? o : $"\"{o}\""));
158 }
159  
160 ///////////////////////////////////////////////////////////////////////////
161 // Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3 //
162 ///////////////////////////////////////////////////////////////////////////
163 /// <summary>
164 /// Converts a comma-separated list of values to a list of strings.
165 /// </summary>
166 /// <param name="csv">a comma-separated list of values</param>
167 /// <returns>a list of strings</returns>
168 /// <remarks>compliant with RFC 4180</remarks>
169 private static IEnumerable<string> FromStringCsv(string csv)
170 {
171 if (string.IsNullOrEmpty(csv))
172 {
173 yield break;
174 }
175  
176 var s = new Stack<char>();
177 var m = new StringBuilder();
178  
179 for (var i = 0; i < csv.Length; ++i)
180 {
181 switch (csv[i])
182 {
183 case ',':
184  
185 if (!s.Any() ||
186 !s.Peek()
187 .Equals('"'))
188 {
189 yield return m.ToString();
190  
191 m = new StringBuilder();
192  
193 continue;
194 }
195  
196 m.Append(csv[i]);
197  
198 continue;
199  
200 case '"':
201  
202 if (i + 1 < csv.Length &&
203 csv[i]
204 .Equals(csv[i + 1]))
205 {
206 m.Append(csv[i]);
207 ++i;
208  
209 continue;
210 }
211  
212 if (!s.Any() ||
213 !s.Peek()
214 .Equals(csv[i]))
215 {
216 s.Push(csv[i]);
217  
218 continue;
219 }
220  
221 s.Pop();
222  
223 continue;
224 }
225  
226 m.Append(csv[i]);
227 }
228  
229 yield return m.ToString();
230 }
231  
232 #endregion
233 }
234 }