'use strict'
// See https://tools.ietf.org/html/rfc4647#section-3.1
// for more information on the algorithms.
exports.basicFilter = factory(basic, true)
exports.extendedFilter = factory(extended, true)
exports.lookup = factory(lookup)
// Basic Filtering (Section 3.3.1) matches a language priority list consisting
// of basic language ranges (Section 2.1) to sets of language tags.
function basic(tag, range) {
return range === '*' || tag === range || tag.indexOf(range + '-') > -1
}
// Extended Filtering (Section 3.3.2) matches a language priority list
// consisting of extended language ranges (Section 2.2) to sets of language
// tags.
function extended(tag, range) {
// 3.3.2.1
var left = tag.split('-')
var right = range.split('-')
var leftIndex = 0
var rightIndex = 0
// 3.3.2.2
if (right[rightIndex] !== '*' && left[leftIndex] !== right[rightIndex]) {
return false
}
leftIndex++
rightIndex++
// 3.3.2.3
while (rightIndex < right.length) {
// 3.3.2.3.A
if (right[rightIndex] === '*') {
rightIndex++
continue
}
// 3.3.2.3.B
if (!left[leftIndex]) return false
// 3.3.2.3.C
if (left[leftIndex] === right[rightIndex]) {
leftIndex++
rightIndex++
continue
}
// 3.3.2.3.D
if (left[leftIndex].length === 1) return false
// 3.3.2.3.E
leftIndex++
}
// 3.3.2.4
return true
}
// Lookup (Section 3.4) matches a language priority list consisting of basic
// language ranges to sets of language tags to find the one exact language tag
// that best matches the range.
function lookup(tag, range) {
var right = range
var index
/* eslint-disable-next-line no-constant-condition */
while (true) {
if (right === '*' || tag === right) return true
index = right.lastIndexOf('-')
if (index < 0) return false
if (right.charAt(index - 2) === '-') index -= 2
right = right.slice(0, index)
}
}
// Factory to perform a filter or a lookup.
// This factory creates a function that accepts a list of tags and a list of
// ranges, and contains logic to exit early for lookups.
// `check` just has to deal with one tag and one range.
// This match function iterates over ranges, and for each range,
// iterates over tags. That way, earlier ranges matching any tag have
// precedence over later ranges.
function factory(check, filter) {
return match
function match(tags, ranges) {
var left = cast(tags, 'tag')
var right = cast(ranges == null ? '*' : ranges, 'range')
var matches = []
var rightIndex = -1
var range
var leftIndex
var next
while (++rightIndex < right.length) {
range = right[rightIndex].toLowerCase()
// Ignore wildcards in lookup mode.
if (!filter && range === '*') continue
leftIndex = -1
next = []
while (++leftIndex < left.length) {
if (check(left[leftIndex].toLowerCase(), range)) {
// Exit if this is a lookup and we have a match.
if (!filter) return left[leftIndex]
matches.push(left[leftIndex])
} else {
next.push(left[leftIndex])
}
}
left = next
}
// If this is a filter, return the list. If it’s a lookup, we didn’t find
// a match, so return `undefined`.
return filter ? matches : undefined
}
}
// Validate tags or ranges, and cast them to arrays.
function cast(values, name) {
var value = values && typeof values === 'string' ? [values] : values
if (!value || typeof value !== 'object' || !('length' in value)) {
throw new Error(
'Invalid ' + name + ' `' + value + '`, expected non-empty string'
)
}
return value
}