namespace CSharpVitamins
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
///
/// Represents a weighted value (or quality value) from an http header e.g. gzip=0.9; deflate; x-gzip=0.5;
///
///
/// accept-encoding spec:
/// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
///
///
/// Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
/// Accept-Encoding: gzip,deflate
/// Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
/// Accept-Language: en-us,en;q=0.5
///
[DebuggerDisplay("QValue[{Name}, {Weight}]")]
public struct QValue : IComparable
{
static char[] delimiters = { ';', '=' };
const float defaultWeight = 1;
#region Fields
string _name;
float _weight;
int _ordinal;
#endregion
#region Constructors
///
/// Creates a new QValue by parsing the given value
/// for name and weight (qvalue)
///
/// The value to be parsed e.g. gzip=0.3
public QValue(string value)
: this(value, 0)
{ }
///
/// Creates a new QValue by parsing the given value
/// for name and weight (qvalue) and assigns the given
/// ordinal
///
/// The value to be parsed e.g. gzip=0.3
/// The ordinal/index where the item
/// was found in the original list.
public QValue(string value, int ordinal)
{
_name = null;
_weight = 0;
_ordinal = ordinal;
ParseInternal(ref this, value);
}
#endregion
#region Properties
///
/// The name of the value part
///
public string Name
{
get { return _name; }
}
///
/// The weighting (or qvalue, quality value) of the encoding
///
public float Weight
{
get { return _weight; }
}
///
/// Whether the value can be accepted
/// i.e. it's weight is greater than zero
///
public bool CanAccept
{
get { return _weight > 0; }
}
///
/// Whether the value is empty (i.e. has no name)
///
public bool IsEmpty
{
get { return string.IsNullOrEmpty(_name); }
}
#endregion
#region Methods
///
/// Parses the given string for name and
/// weigth (qvalue)
///
/// The string to parse
public static QValue Parse(string value)
{
QValue item = new QValue();
ParseInternal(ref item, value);
return item;
}
///
/// Parses the given string for name and
/// weigth (qvalue)
///
/// The string to parse
/// The order of item in sequence
///
public static QValue Parse(string value, int ordinal)
{
QValue item = Parse(value);
item._ordinal = ordinal;
return item;
}
///
/// Parses the given string for name and
/// weigth (qvalue)
///
/// The string to parse
static void ParseInternal(ref QValue target, string value)
{
string[] parts = value.Split(delimiters, 3);
if (parts.Length > 0)
{
target._name = parts[0].Trim();
target._weight = defaultWeight;
}
if (parts.Length == 3)
{
float.TryParse(parts[2], out target._weight);
}
}
#endregion
#region IComparable Members
///
/// Compares this instance to another QValue by
/// comparing first weights, then ordinals.
///
/// The QValue to compare
///
public int CompareTo(QValue other)
{
int value = _weight.CompareTo(other._weight);
if (value == 0)
{
int ord = -_ordinal;
value = ord.CompareTo(-other._ordinal);
}
return value;
}
#endregion
#region CompareByWeight
///
/// Compares two QValues in ascending order.
///
/// The first QValue
/// The second QValue
///
public static int CompareByWeightAsc(QValue x, QValue y)
{
return x.CompareTo(y);
}
///
/// Compares two QValues in descending order.
///
/// The first QValue
/// The second QValue
///
public static int CompareByWeightDesc(QValue x, QValue y)
{
return -x.CompareTo(y);
}
#endregion
}
///
/// Provides a collection for working with qvalue http headers
///
///
/// accept-encoding spec:
/// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
///
[DebuggerDisplay("QValue[{Count}, {AcceptWildcard}]")]
public sealed class QValueList : List
{
static char[] delimiters = { ',' };
#region Fields
bool _acceptWildcard;
bool _autoSort;
#endregion
#region Constructors
///
/// Creates a new instance of an QValueList list from
/// the given string of comma delimited values
///
/// The raw string of qvalues to load
public QValueList(string values)
: this(null == values ? new string[0] : values.Split(delimiters, StringSplitOptions.RemoveEmptyEntries))
{ }
///
/// Creates a new instance of an QValueList from
/// the given string array of qvalues
///
/// The array of qvalue strings
/// i.e. name(;q=[0-9\.]+)?
///
/// Should AcceptWildcard include */* as well?
/// What about other wildcard forms?
///
public QValueList(string[] values)
{
int ordinal = -1;
foreach (string value in values)
{
QValue qvalue = QValue.Parse(value.Trim(), ++ordinal);
if (qvalue.Name.Equals("*")) // wildcard
_acceptWildcard = qvalue.CanAccept;
Add(qvalue);
}
/// this list should be sorted by weight for
/// methods like FindPreferred to work correctly
DefaultSort();
_autoSort = true;
}
#endregion
#region Properties
///
/// Whether or not the wildcarded encoding is available and allowed
///
public bool AcceptWildcard
{
get { return _acceptWildcard; }
}
///
/// Whether, after an add operation, the list should be resorted
///
public bool AutoSort
{
get { return _autoSort; }
set { _autoSort = value; }
}
///
/// Synonym for FindPreferred
///
/// The preferred order in which to return an encoding
/// An QValue based on weight, or null
public QValue this[params string[] candidates]
{
get { return FindPreferred(candidates); }
}
#endregion
#region Add
///
/// Adds an item to the list, then applies sorting
/// if AutoSort is enabled.
///
/// The item to add
public new void Add(QValue item)
{
base.Add(item);
applyAutoSort();
}
#endregion
#region AddRange
///
/// Adds a range of items to the list, then applies sorting
/// if AutoSort is enabled.
///
/// The items to add
public new void AddRange(IEnumerable collection)
{
bool state = _autoSort;
_autoSort = false;
base.AddRange(collection);
_autoSort = state;
applyAutoSort();
}
#endregion
#region Find
///
/// Finds the first QValue with the given name (case-insensitive)
///
/// The name of the QValue to search for
///
public QValue Find(string name)
{
Predicate criteria = delegate(QValue item) { return item.Name.Equals(name, StringComparison.OrdinalIgnoreCase); };
return Find(criteria);
}
#endregion
#region FindHighestWeight
///
/// Returns the first match found from the given candidates
///
/// The list of QValue names to find
/// The first QValue match to be found
/// Loops from the first item in the list to the last and finds
/// the first candidate - the list must be sorted for weight prior to
/// calling this method.
public QValue FindHighestWeight(params string[] candidates)
{
Predicate criteria = delegate(QValue item)
{
return isCandidate(item.Name, candidates);
};
return Find(criteria);
}
#endregion
#region FindPreferred
///
/// Returns the first match found from the given candidates that is accepted
///
/// The list of names to find
/// The first QValue match to be found
/// Loops from the first item in the list to the last and finds the
/// first candidate that can be accepted - the list must be sorted for weight
/// prior to calling this method.
public QValue FindPreferred(params string[] candidates)
{
Predicate criteria = delegate(QValue item)
{
return isCandidate(item.Name, candidates) && item.CanAccept;
};
return Find(criteria);
}
#endregion
#region DefaultSort
///
/// Sorts the list comparing by weight in
/// descending order
///
public void DefaultSort()
{
Sort(QValue.CompareByWeightDesc);
}
#endregion
#region applyAutoSort
///
/// Applies the default sorting method if
/// the autosort field is currently enabled
///
void applyAutoSort()
{
if (_autoSort)
DefaultSort();
}
#endregion
#region isCandidate
///
/// Determines if the given item contained within the applied array
/// (case-insensitive)
///
/// The string to search for
/// The array to search in
///
static bool isCandidate(string item, params string[] candidates)
{
foreach (string candidate in candidates)
{
if (candidate.Equals(item, StringComparison.OrdinalIgnoreCase))
return true;
}
return false;
}
#endregion
}
}