wasSharp – Blame information for rev 27

Subversion Repositories:
Rev:
Rev Author Line No. Line
17 office 1 // The MIT License (MIT)
27 office 2 //
17 office 3 // Copyright (c) 2015 Dave Transom
27 office 4 //
17 office 5 // Permission is hereby granted, free of charge, to any person obtaining a
27 office 6 // copy of this software and associated documentation files (the
17 office 7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
27 office 12 //
17 office 13 // The above copyright notice and this permission notice shall be included
14 // in all copies or substantial portions of the Software.
27 office 15 //
17 office 16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 // IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 // TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 // SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23  
24 // For background refer to this article by Dave Transom
25 // http://www.singular.co.nz/2008/07/finding-preferred-accept-encoding-header-in-csharp/
26  
27 using System;
28 using System.Collections.Generic;
29 using System.Diagnostics;
30 using System.Globalization;
31  
32 namespace wasSharp.Web
33 {
34 /// <summary>
35 /// Represents a weighted value (or quality value) from an http header e.g. gzip=0.9; deflate; x-gzip=0.5;
36 /// </summary>
37 /// <remarks>
38 /// accept-encoding spec:
39 /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
40 /// </remarks>
41 /// <example>
42 /// Accept:
43 /// text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
44 /// Accept-Encoding: gzip,deflate
45 /// Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
46 /// Accept-Language: en-us,en;q=0.5
47 /// </example>
48 [DebuggerDisplay("QValue[{Name}, {Weight}]")]
49 public struct QValue : IComparable<QValue>
50 {
27 office 51 private static readonly char[] delimiters = { ';', '=' };
17 office 52 private const float defaultWeight = 1;
53  
54 #region Fields
55  
56 private float _weight;
57 private int _ordinal;
58  
27 office 59 #endregion Fields
17 office 60  
61 #region Constructors
62  
63 /// <summary>
64 /// Creates a new QValue by parsing the given value
65 /// for name and weight (qvalue)
66 /// </summary>
67 /// <param name="value">The value to be parsed e.g. gzip=0.3</param>
68 public QValue(string value)
69 : this(value, 0)
70 {
71 }
72  
73 /// <summary>
74 /// Creates a new QValue by parsing the given value
75 /// for name and weight (qvalue) and assigns the given
76 /// ordinal
77 /// </summary>
78 /// <param name="value">The value to be parsed e.g. gzip=0.3</param>
79 /// <param name="ordinal">
80 /// The ordinal/index where the item
81 /// was found in the original list.
82 /// </param>
83 public QValue(string value, int ordinal)
84 {
85 Name = null;
86 _weight = 0;
87 _ordinal = ordinal;
88  
89 ParseInternal(ref this, value);
90 }
91  
27 office 92 #endregion Constructors
17 office 93  
94 #region Properties
95  
96 /// <summary>
97 /// The name of the value part
98 /// </summary>
99 public string Name { get; private set; }
100  
101 /// <summary>
102 /// The weighting (or qvalue, quality value) of the encoding
103 /// </summary>
104 public float Weight => _weight;
105  
106 /// <summary>
107 /// Whether the value can be accepted
108 /// i.e. it's weight is greater than zero
109 /// </summary>
110 public bool CanAccept => _weight > 0;
111  
112 /// <summary>
113 /// Whether the value is empty (i.e. has no name)
114 /// </summary>
115 public bool IsEmpty => string.IsNullOrEmpty(Name);
116  
27 office 117 #endregion Properties
17 office 118  
119 #region Methods
120  
121 /// <summary>
122 /// Parses the given string for name and
123 /// weigth (qvalue)
124 /// </summary>
125 /// <param name="value">The string to parse</param>
126 public static QValue Parse(string value)
127 {
128 var item = new QValue();
129 ParseInternal(ref item, value);
130 return item;
131 }
132  
133 /// <summary>
134 /// Parses the given string for name and
135 /// weigth (qvalue)
136 /// </summary>
137 /// <param name="value">The string to parse</param>
138 /// <param name="ordinal">The order of item in sequence</param>
139 /// <returns></returns>
140 public static QValue Parse(string value, int ordinal)
141 {
142 var item = Parse(value);
143 item._ordinal = ordinal;
144 return item;
145 }
146  
147 /// <summary>
148 /// Parses the given string for name and
149 /// weigth (qvalue)
150 /// </summary>
151 /// <param name="value">The string to parse</param>
152 private static void ParseInternal(ref QValue target, string value)
153 {
154 var parts = value.Split(delimiters, 3);
155 if (parts.Length > 0)
156 {
157 target.Name = parts[0].Trim();
158 target._weight = defaultWeight;
159 }
160  
161 if (parts.Length == 3)
162 {
163 float.TryParse(parts[2], NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture.NumberFormat,
164 out target._weight);
165 }
166 }
167  
27 office 168 #endregion Methods
17 office 169  
170 #region IComparable<QValue> Members
171  
172 /// <summary>
173 /// Compares this instance to another QValue by
174 /// comparing first weights, then ordinals.
175 /// </summary>
176 /// <param name="other">The QValue to compare</param>
177 /// <returns></returns>
178 public int CompareTo(QValue other)
179 {
180 var value = _weight.CompareTo(other._weight);
181 if (value == 0)
182 {
183 var ord = -_ordinal;
184 value = ord.CompareTo(-other._ordinal);
185 }
186 return value;
187 }
188  
27 office 189 #endregion IComparable<QValue> Members
17 office 190  
191 #region CompareByWeight
192  
193 /// <summary>
194 /// Compares two QValues in ascending order.
195 /// </summary>
196 /// <param name="x">The first QValue</param>
197 /// <param name="y">The second QValue</param>
198 /// <returns></returns>
199 public static int CompareByWeightAsc(QValue x, QValue y)
200 {
201 return x.CompareTo(y);
202 }
203  
204 /// <summary>
205 /// Compares two QValues in descending order.
206 /// </summary>
207 /// <param name="x">The first QValue</param>
208 /// <param name="y">The second QValue</param>
209 /// <returns></returns>
210 public static int CompareByWeightDesc(QValue x, QValue y)
211 {
212 return -x.CompareTo(y);
213 }
214  
27 office 215 #endregion CompareByWeight
17 office 216 }
217  
218 /// <summary>
219 /// Provides a collection for working with qvalue http headers
220 /// </summary>
221 /// <remarks>
222 /// accept-encoding spec:
223 /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
224 /// </remarks>
225 [DebuggerDisplay("QValue[{Count}, {AcceptWildcard}]")]
226 public sealed class QValueList : List<QValue>
227 {
27 office 228 private static readonly char[] delimiters = { ',' };
17 office 229  
230 #region Add
231  
232 /// <summary>
233 /// Adds an item to the list, then applies sorting
234 /// if AutoSort is enabled.
235 /// </summary>
236 /// <param name="item">The item to add</param>
237 public new void Add(QValue item)
238 {
239 base.Add(item);
240  
241 applyAutoSort();
242 }
243  
27 office 244 #endregion Add
17 office 245  
246 #region AddRange
247  
248 /// <summary>
249 /// Adds a range of items to the list, then applies sorting
250 /// if AutoSort is enabled.
251 /// </summary>
252 /// <param name="collection">The items to add</param>
253 public new void AddRange(IEnumerable<QValue> collection)
254 {
255 var state = AutoSort;
256 AutoSort = false;
257  
258 base.AddRange(collection);
259  
260 AutoSort = state;
261 applyAutoSort();
262 }
263  
27 office 264 #endregion AddRange
17 office 265  
266 #region Find
267  
268 /// <summary>
269 /// Finds the first QValue with the given name (case-insensitive)
270 /// </summary>
271 /// <param name="name">The name of the QValue to search for</param>
272 /// <returns></returns>
273 public QValue Find(string name)
274 {
275 Predicate<QValue> criteria =
276 item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase);
277 return Find(criteria);
278 }
279  
27 office 280 #endregion Find
17 office 281  
282 #region FindHighestWeight
283  
284 /// <summary>
285 /// Returns the first match found from the given candidates
286 /// </summary>
287 /// <param name="candidates">The list of QValue names to find</param>
288 /// <returns>The first QValue match to be found</returns>
289 /// <remarks>
290 /// Loops from the first item in the list to the last and finds
291 /// the first candidate - the list must be sorted for weight prior to
292 /// calling this method.
293 /// </remarks>
294 public QValue FindHighestWeight(params string[] candidates)
295 {
296 Predicate<QValue> criteria = item => isCandidate(item.Name, candidates);
297 return Find(criteria);
298 }
299  
27 office 300 #endregion FindHighestWeight
17 office 301  
302 #region FindPreferred
303  
304 /// <summary>
305 /// Returns the first match found from the given candidates that is accepted
306 /// </summary>
307 /// <param name="candidates">The list of names to find</param>
308 /// <returns>The first QValue match to be found</returns>
309 /// <remarks>
310 /// Loops from the first item in the list to the last and finds the
311 /// first candidate that can be accepted - the list must be sorted for weight
312 /// prior to calling this method.
313 /// </remarks>
314 public QValue FindPreferred(params string[] candidates)
315 {
316 Predicate<QValue> criteria =
317 item => isCandidate(item.Name, candidates) && item.CanAccept;
318 return Find(criteria);
319 }
320  
27 office 321 #endregion FindPreferred
17 office 322  
323 #region DefaultSort
324  
325 /// <summary>
326 /// Sorts the list comparing by weight in
327 /// descending order
328 /// </summary>
329 public void DefaultSort()
330 {
331 Sort(QValue.CompareByWeightDesc);
332 }
333  
27 office 334 #endregion DefaultSort
17 office 335  
336 #region applyAutoSort
337  
338 /// <summary>
339 /// Applies the default sorting method if
340 /// the autosort field is currently enabled
341 /// </summary>
342 private void applyAutoSort()
343 {
344 if (AutoSort)
345 DefaultSort();
346 }
347  
27 office 348 #endregion applyAutoSort
17 office 349  
350 #region isCandidate
351  
352 /// <summary>
353 /// Determines if the given item contained within the applied array
354 /// (case-insensitive)
355 /// </summary>
356 /// <param name="item">The string to search for</param>
357 /// <param name="candidates">The array to search in</param>
358 /// <returns></returns>
359 private static bool isCandidate(string item, params string[] candidates)
360 {
361 foreach (var candidate in candidates)
362 {
363 if (candidate.Equals(item, StringComparison.OrdinalIgnoreCase))
364 return true;
365 }
366 return false;
367 }
368  
27 office 369 #endregion isCandidate
17 office 370  
371 #region Constructors
372  
373 /// <summary>
374 /// Creates a new instance of an QValueList list from
375 /// the given string of comma delimited values
376 /// </summary>
377 /// <param name="values">The raw string of qvalues to load</param>
378 public QValueList(string values)
379 : this(null == values ? new string[0] : values.Split(delimiters, StringSplitOptions.RemoveEmptyEntries))
380 {
381 }
382  
383 /// <summary>
384 /// Creates a new instance of an QValueList from
385 /// the given string array of qvalues
386 /// </summary>
387 /// <param name="values">
388 /// The array of qvalue strings
389 /// i.e. name(;q=[0-9\.]+)?
390 /// </param>
391 /// <remarks>
392 /// Should AcceptWildcard include */* as well?
393 /// What about other wildcard forms?
394 /// </remarks>
395 public QValueList(string[] values)
396 {
397 var ordinal = -1;
398 foreach (var value in values)
399 {
400 var qvalue = QValue.Parse(value.Trim(), ++ordinal);
401 if (qvalue.Name.Equals("*")) // wildcard
402 AcceptWildcard = qvalue.CanAccept;
403 Add(qvalue);
404 }
405  
406 /// this list should be sorted by weight for
407 /// methods like FindPreferred to work correctly
408 DefaultSort();
409 AutoSort = true;
410 }
411  
27 office 412 #endregion Constructors
17 office 413  
414 #region Properties
415  
416 /// <summary>
417 /// Whether or not the wildcarded encoding is available and allowed
418 /// </summary>
419 public bool AcceptWildcard { get; }
420  
421 /// <summary>
422 /// Whether, after an add operation, the list should be resorted
423 /// </summary>
424 public bool AutoSort { get; set; }
425  
426 /// <summary>
427 /// Synonym for FindPreferred
428 /// </summary>
429 /// <param name="candidates">The preferred order in which to return an encoding</param>
430 /// <returns>An QValue based on weight, or null</returns>
431 public QValue this[params string[] candidates] => FindPreferred(candidates);
432  
27 office 433 #endregion Properties
17 office 434 }
27 office 435 }