1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
module Rack::Accept
# Contains methods that are useful for working with Accept-style HTTP
# headers. The MediaType, Charset, Encoding, and Language classes all mixin
# this module.
module Header
# Parses the value of an Accept-style request header into a hash of
# acceptable values and their respective quality factors (qvalues). The
# +join+ method may be used on the resulting hash to obtain a header
# string that is the semantic equivalent of the one provided.
def parse(header)
qvalues = {}
header.to_s.split(/,\s*/).each do |part|
m = /^([^\s,]+?)(?:\s*;\s*q\s*=\s*(\d+(?:\.\d+)?))?$/.match(part)
if m
qvalues[m[1].downcase] = normalize_qvalue((m[2] || 1).to_f)
else
raise "Invalid header value: #{part.inspect}"
end
end
qvalues
end
module_function :parse
# Returns a string suitable for use as the value of an Accept-style HTTP
# header from the map of acceptable values to their respective quality
# factors (qvalues). The +parse+ method may be used on the resulting string
# to obtain a hash that is the equivalent of the one provided.
def join(qvalues)
qvalues.map {|k, v| k + (v == 1 ? '' : ";q=#{v}") }.join(', ')
end
module_function :join
# Parses a media type string into its relevant pieces. The return value
# will be an array with three values: 1) the content type, 2) the content
# subtype, and 3) the media type parameters. An empty array is returned if
# no match can be made.
def parse_media_type(media_type)
m = media_type.to_s.match(/^([a-z*]+)\/([a-z0-9*\-.+]+)(?:;([a-z0-9=;]+))?$/)
m ? [m[1], m[2], m[3] || ''] : []
end
module_function :parse_media_type
# Parses a string of media type range parameters into a hash of parameters
# to their respective values.
def parse_range_params(params)
params.split(';').inject({}) do |m, p|
k, v = p.split('=', 2)
m[k] = v if v
m
end
end
module_function :parse_range_params
# Converts 1.0 and 0.0 qvalues to 1 and 0 respectively. Used to maintain
# consistency across qvalue methods.
def normalize_qvalue(q)
(q == 1 || q == 0) && q.is_a?(Float) ? q.to_i : q
end
module_function :normalize_qvalue
module PublicInstanceMethods
# A table of all values of this header to their respective quality
# factors (qvalues).
attr_accessor :qvalues
def initialize(header='')
@qvalues = parse(header)
end
# The name of this header. Should be overridden in classes that mixin
# this module.
def name
''
end
# Returns the quality factor (qvalue) of the given +value+. Should be
# overridden in classes that mixin this module.
def qvalue(value)
1
end
# Returns the value of this header as a string.
def value
join(@qvalues)
end
# Returns an array of all values of this header, in no particular order.
def values
@qvalues.keys
end
# Determines if the given +value+ is acceptable (does not have a qvalue
# of 0) according to this header.
def accept?(value)
qvalue(value) != 0
end
# Returns a copy of the given +values+ array, sorted by quality factor
# (qvalue). Each element of the returned array is itself an array
# containing two objects: 1) the value's qvalue and 2) the original
# value.
#
# It is important to note that this sort is a "stable sort". In other
# words, the order of the original values is preserved so long as the
# qvalue for each is the same. This expectation can be useful when
# trying to determine which of a variety of options has the highest
# qvalue. If the user prefers using one option over another (for any
# number of reasons), he should put it first in +values+. He may then
# use the first result with confidence that it is both most acceptable
# to the client and most convenient for him as well.
def sort_with_qvalues(values, keep_unacceptables=true)
qvalues = {}
values.each do |v|
q = qvalue(v)
if q != 0 || keep_unacceptables
qvalues[q] ||= []
qvalues[q] << v
end
end
order = qvalues.keys.sort.reverse
order.inject([]) {|m, q| m.concat(qvalues[q].map {|v| [q, v] }) }
end
# Sorts the given +values+ according to the qvalue of each while
# preserving the original order. See #sort_with_qvalues for more
# information on exactly how the sort is performed.
def sort(values, keep_unacceptables=false)
sort_with_qvalues(values, keep_unacceptables).map {|q, v| v }
end
# A shortcut for retrieving the first result of #sort.
def best_of(values, keep_unacceptables=false)
sort(values, keep_unacceptables).first
end
# Returns a string representation of this header.
def to_s
[name, value].join(': ')
end
end
include PublicInstanceMethods
end
end
|