///|
fn parse_language_core(
raw : String,
order : Int,
) -> Result[(String, Array[String]), ParseError] {
let tag = clean(raw[:]).to_lower()
if tag == "*" {
return Ok(("*", []))
}
if tag.is_empty() {
return Err(InvalidLanguageTag(order, tag))
}
let parts = split_quoted(tag, '-')
let subtags : Array[String] = []
for i in 0.. 0 {
subtags.push(part)
}
}
Ok((parts[0], subtags))
}
///|
fn parse_language_range_item(
raw : String,
order : Int,
) -> Result[LanguageRange, ParseError] {
let pieces = split_quoted(raw, ';')
let main = clean(pieces[0][:])
let (primary, subtags) = match parse_language_core(main, order) {
Ok(value) => value
Err(err) => return Err(err)
}
let mut q = 1000
for i in 1..
if param.name == "q" {
match parse_q(param.value, order) {
Ok(parsed) => q = parsed
Err(err) => return Err(err)
}
} else {
return Err(InvalidParameter(order, piece))
}
Err(err) => return Err(err)
}
}
}
Ok({ primary, subtags, q, order, raw: clean(raw[:]) })
}
///|
fn parse_language_offer_item(
raw : String,
index : Int,
) -> Result[LanguageOffer, ParseError] {
match parse_language_core(raw, index) {
Ok((primary, subtags)) =>
if primary == "*" {
Err(InvalidOffer(index, raw))
} else {
Ok({ tag: clean(raw[:]), primary, subtags, index })
}
Err(err) => Err(err)
}
}
///|
pub fn parse_accept_language(
header : StringView,
) -> Result[Array[LanguageRange], ParseError] {
let text = clean(header)
let ranges : Array[LanguageRange] = []
if text.is_empty() {
return Ok(ranges)
}
let parts = split_quoted(text, ',')
for i in 0.. ranges.push(range)
Err(err) => return Err(err)
}
}
Ok(ranges)
}
///|
pub fn parse_language_tag(
tag : StringView,
) -> Result[LanguageOffer, ParseError] {
parse_language_offer_item(clean(tag), 0)
}
///|
fn default_language_range() -> LanguageRange {
{ primary: "*", subtags: [], q: 1000, order: 0, raw: "*" }
}
///|
fn language_specificity(range : LanguageRange) -> Int {
if range.primary == "*" {
0
} else {
1 + range.subtags.length()
}
}
///|
fn language_range_matches(range : LanguageRange, offer : LanguageOffer) -> Bool {
if range.primary == "*" {
return true
}
if range.primary != offer.primary {
return false
}
if range.subtags.length() > offer.subtags.length() {
return false
}
for i in 0.. String {
let buf = StringBuilder()
buf.write_string(range.primary)
for sub in range.subtags {
buf.write_char('-')
buf.write_string(sub)
}
if range.q != 1000 {
buf.write_string(";q=")
buf.write_string(quality_to_string(range.q))
}
buf.to_string()
}
///|
fn language_reason(
offer : LanguageOffer,
range : LanguageRange,
specificity : Int,
) -> String {
let buf = StringBuilder()
buf.write_string(offer.tag)
buf.write_string(" matched ")
buf.write_string(format_language_range(range))
buf.write_string(" with q=")
buf.write_string(quality_to_string(range.q))
buf.write_string(", specificity=")
buf.write_string(specificity.to_string())
buf.to_string()
}
///|
fn better_language(
candidate : LanguageCandidate,
current : LanguageCandidate,
) -> Bool {
if candidate.quality != current.quality {
candidate.quality > current.quality
} else if candidate.specificity != current.specificity {
candidate.specificity > current.specificity
} else if candidate.matched_range.order != current.matched_range.order {
candidate.matched_range.order < current.matched_range.order
} else {
candidate.offer.index < current.offer.index
}
}
///|
fn better_language_range_for_offer(
candidate : LanguageRange,
current : LanguageRange,
) -> Bool {
let candidate_specificity = language_specificity(candidate)
let current_specificity = language_specificity(current)
if candidate_specificity != current_specificity {
candidate_specificity > current_specificity
} else {
candidate.order < current.order
}
}
///|
fn effective_language_range(
offer : LanguageOffer,
ranges : Array[LanguageRange],
) -> LanguageRange? {
let mut best : LanguageRange? = None
for range in ranges {
if language_range_matches(range, offer) {
if (match best {
None => true
Some(current) => better_language_range_for_offer(range, current)
}) {
best = Some(range)
}
}
}
best
}
///|
fn language_explanation(best : LanguageCandidate?) -> String {
match best {
Some(candidate) => {
let buf = StringBuilder()
buf.write_string("selected ")
buf.write_string(candidate.offer.tag)
buf.write_string(": ")
buf.write_string(candidate.reason)
buf.to_string()
}
None => "no acceptable language offer matched"
}
}
///|
pub fn negotiate_language(
accept_language : StringView,
offers : Array[String],
) -> Result[LanguageDecision, ParseError] {
let ranges = match parse_accept_language(accept_language) {
Ok(parsed) =>
if parsed.length() == 0 {
[default_language_range()]
} else {
parsed
}
Err(err) => return Err(err)
}
let matches : Array[LanguageCandidate] = []
let mut best : LanguageCandidate? = None
for offer_index in 0.. offer
Err(err) => return Err(err)
}
match effective_language_range(offer, ranges) {
Some(range) =>
if range.q > 0 {
let specificity = language_specificity(range)
let candidate : LanguageCandidate = {
offer,
matched_range: range,
quality: range.q,
specificity,
reason: language_reason(offer, range, specificity),
}
matches.push(candidate)
if (match best {
None => true
Some(current) => better_language(candidate, current)
}) {
best = Some(candidate)
}
}
None => ()
}
}
let selected = match best {
Some(candidate) => Some(candidate.offer.tag)
None => None
}
let quality = match best {
Some(candidate) => candidate.quality
None => 0
}
Ok({
selected,
quality,
candidate: best,
matches,
explanation: language_explanation(best),
})
}