wasSharp – Blame information for rev 33
?pathlinks?
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 | } |