1/**
2 * negotiator
3 * Copyright(c) 2012 Isaac Z. Schlueter
4 * Copyright(c) 2014 Federico Romero
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
6 * MIT Licensed
7 */
8
9'use strict';
10
11/**
12 * Module exports.
13 * @public
14 */
15
16module.exports = preferredCharsets;
17module.exports.preferredCharsets = preferredCharsets;
18
19/**
20 * Module variables.
21 * @private
22 */
23
24var simpleCharsetRegExp = /^\s*([^\s;]+)\s*(?:;(.*))?$/;
25
26/**
27 * Parse the Accept-Charset header.
28 * @private
29 */
30
31function parseAcceptCharset(accept) {
32  var accepts = accept.split(',');
33
34  for (var i = 0, j = 0; i < accepts.length; i++) {
35    var charset = parseCharset(accepts[i].trim(), i);
36
37    if (charset) {
38      accepts[j++] = charset;
39    }
40  }
41
42  // trim accepts
43  accepts.length = j;
44
45  return accepts;
46}
47
48/**
49 * Parse a charset from the Accept-Charset header.
50 * @private
51 */
52
53function parseCharset(str, i) {
54  var match = simpleCharsetRegExp.exec(str);
55  if (!match) return null;
56
57  var charset = match[1];
58  var q = 1;
59  if (match[2]) {
60    var params = match[2].split(';')
61    for (var j = 0; j < params.length; j++) {
62      var p = params[j].trim().split('=');
63      if (p[0] === 'q') {
64        q = parseFloat(p[1]);
65        break;
66      }
67    }
68  }
69
70  return {
71    charset: charset,
72    q: q,
73    i: i
74  };
75}
76
77/**
78 * Get the priority of a charset.
79 * @private
80 */
81
82function getCharsetPriority(charset, accepted, index) {
83  var priority = {o: -1, q: 0, s: 0};
84
85  for (var i = 0; i < accepted.length; i++) {
86    var spec = specify(charset, accepted[i], index);
87
88    if (spec && (priority.s - spec.s || priority.q - spec.q || priority.o - spec.o) < 0) {
89      priority = spec;
90    }
91  }
92
93  return priority;
94}
95
96/**
97 * Get the specificity of the charset.
98 * @private
99 */
100
101function specify(charset, spec, index) {
102  var s = 0;
103  if(spec.charset.toLowerCase() === charset.toLowerCase()){
104    s |= 1;
105  } else if (spec.charset !== '*' ) {
106    return null
107  }
108
109  return {
110    i: index,
111    o: spec.i,
112    q: spec.q,
113    s: s
114  }
115}
116
117/**
118 * Get the preferred charsets from an Accept-Charset header.
119 * @public
120 */
121
122function preferredCharsets(accept, provided) {
123  // RFC 2616 sec 14.2: no header = *
124  var accepts = parseAcceptCharset(accept === undefined ? '*' : accept || '');
125
126  if (!provided) {
127    // sorted list of all charsets
128    return accepts
129      .filter(isQuality)
130      .sort(compareSpecs)
131      .map(getFullCharset);
132  }
133
134  var priorities = provided.map(function getPriority(type, index) {
135    return getCharsetPriority(type, accepts, index);
136  });
137
138  // sorted list of accepted charsets
139  return priorities.filter(isQuality).sort(compareSpecs).map(function getCharset(priority) {
140    return provided[priorities.indexOf(priority)];
141  });
142}
143
144/**
145 * Compare two specs.
146 * @private
147 */
148
149function compareSpecs(a, b) {
150  return (b.q - a.q) || (b.s - a.s) || (a.o - b.o) || (a.i - b.i) || 0;
151}
152
153/**
154 * Get full charset string.
155 * @private
156 */
157
158function getFullCharset(spec) {
159  return spec.charset;
160}
161
162/**
163 * Check if a spec has any quality.
164 * @private
165 */
166
167function isQuality(spec) {
168  return spec.q > 0;
169}
170