3 * Copyright(c) 2012 Isaac Z. Schlueter
4 * Copyright(c) 2014 Federico Romero
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
9 module.exports = preferredMediaTypes;
10 preferredMediaTypes.preferredMediaTypes = preferredMediaTypes;
12 function parseAccept(accept) {
13 var accepts = splitMediaTypes(accept);
15 for (var i = 0, j = 0; i < accepts.length; i++) {
16 var mediaType = parseMediaType(accepts[i].trim(), i);
19 accepts[j++] = mediaType;
29 function parseMediaType(s, i) {
30 var match = s.match(/\s*(\S+?)\/([^;\s]+)\s*(?:;(.*))?/);
31 if (!match) return null;
35 full = "" + type + "/" + subtype,
40 params = match[3].split(';').map(function(s) {
41 return s.trim().split('=');
42 }).reduce(function (set, p) {
43 var name = p[0].toLowerCase();
46 set[name] = value && value[0] === '"' && value[value.length - 1] === '"'
47 ? value.substr(1, value.length - 2)
53 if (params.q != null) {
54 q = parseFloat(params.q);
69 function getMediaTypePriority(type, accepted, index) {
70 var priority = {o: -1, q: 0, s: 0};
72 for (var i = 0; i < accepted.length; i++) {
73 var spec = specify(type, accepted[i], index);
75 if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
83 function specify(type, spec, index) {
84 var p = parseMediaType(type);
91 if(spec.type.toLowerCase() == p.type.toLowerCase()) {
93 } else if(spec.type != '*') {
97 if(spec.subtype.toLowerCase() == p.subtype.toLowerCase()) {
99 } else if(spec.subtype != '*') {
103 var keys = Object.keys(spec.params);
104 if (keys.length > 0) {
105 if (keys.every(function (k) {
106 return spec.params[k] == '*' || (spec.params[k] || '').toLowerCase() == (p.params[k] || '').toLowerCase();
123 function preferredMediaTypes(accept, provided) {
124 // RFC 2616 sec 14.2: no header = */*
125 var accepts = parseAccept(accept === undefined ? '*/*' : accept || '');
128 // sorted list of all types
129 return accepts.filter(isQuality).sort(compareSpecs).map(function getType(spec) {
134 var priorities = provided.map(function getPriority(type, index) {
135 return getMediaTypePriority(type, accepts, index);
138 // sorted list of accepted types
139 return priorities.filter(isQuality).sort(compareSpecs).map(function getType(priority) {
140 return provided[priorities.indexOf(priority)];
144 function compareSpecs(a, b) {
145 return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
148 function isQuality(spec) {
152 function quoteCount(string) {
156 while ((index = string.indexOf('"', index)) !== -1) {
164 function splitMediaTypes(accept) {
165 var accepts = accept.split(',');
167 for (var i = 1, j = 0; i < accepts.length; i++) {
168 if (quoteCount(accepts[j]) % 2 == 0) {
169 accepts[++j] = accepts[i];
171 accepts[j] += ',' + accepts[i];
176 accepts.length = j + 1;