| // mimeparse.js |
| // |
| // This module provides basic functions for handling mime-types. It can |
| // handle matching mime-types against a list of media-ranges. See section |
| // 14.1 of the HTTP specification [RFC 2616] for a complete explanation. |
| // |
| // http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1 |
| // |
| // A port to JavaScript of Joe Gregorio's MIME-Type Parser: |
| // |
| // http://code.google.com/p/mimeparse/ |
| // |
| // Ported by J. Chris Anderson <jchris@apache.org>, targeting the Spidermonkey runtime. |
| // |
| // To run the tests, open mimeparse-js-test.html in a browser. |
| // Ported from version 0.1.2 |
| // Comments are mostly excerpted from the original. |
| |
| var Mimeparse = (function() { |
| // private helpers |
| function strip(string) { |
| return string.replace(/^\s+/, '').replace(/\s+$/, ''); |
| }; |
| |
| function parseRanges(ranges) { |
| var parsedRanges = [], rangeParts = ranges.split(","); |
| for (var i=0; i < rangeParts.length; i++) { |
| parsedRanges.push(publicMethods.parseMediaRange(rangeParts[i])); |
| }; |
| return parsedRanges; |
| }; |
| |
| var publicMethods = { |
| // Carves up a mime-type and returns an Array of the |
| // [type, subtype, params] where "params" is a Hash of all |
| // the parameters for the media range. |
| // |
| // For example, the media range "application/xhtml;q=0.5" would |
| // get parsed into: |
| // |
| // ["application", "xhtml", { "q" : "0.5" }] |
| parseMimeType : function(mimeType) { |
| var fullType, typeParts, params = {}, parts = mimeType.split(';'); |
| for (var i=0; i < parts.length; i++) { |
| var p = parts[i].split('='); |
| if (p.length == 2) { |
| params[strip(p[0])] = strip(p[1]); |
| } |
| }; |
| fullType = parts[0].replace(/^\s+/, '').replace(/\s+$/, ''); |
| if (fullType == '*') fullType = '*/*'; |
| typeParts = fullType.split('/'); |
| return [typeParts[0], typeParts[1], params]; |
| }, |
| |
| // Carves up a media range and returns an Array of the |
| // [type, subtype, params] where "params" is a Object with |
| // all the parameters for the media range. |
| // |
| // For example, the media range "application/*;q=0.5" would |
| // get parsed into: |
| // |
| // ["application", "*", { "q" : "0.5" }] |
| // |
| // In addition this function also guarantees that there |
| // is a value for "q" in the params dictionary, filling it |
| // in with a proper default if necessary. |
| parseMediaRange : function(range) { |
| var q, parsedType = this.parseMimeType(range); |
| if (!parsedType[2]['q']) { |
| parsedType[2]['q'] = '1'; |
| } else { |
| q = parseFloat(parsedType[2]['q']); |
| if (isNaN(q)) { |
| parsedType[2]['q'] = '1'; |
| } else if (q > 1 || q < 0) { |
| parsedType[2]['q'] = '1'; |
| } |
| } |
| return parsedType; |
| }, |
| |
| // Find the best match for a given mime-type against |
| // a list of media_ranges that have already been |
| // parsed by parseMediaRange(). Returns an array of |
| // the fitness value and the value of the 'q' quality |
| // parameter of the best match, or (-1, 0) if no match |
| // was found. Just as for qualityParsed(), 'parsed_ranges' |
| // must be a list of parsed media ranges. |
| fitnessAndQualityParsed : function(mimeType, parsedRanges) { |
| var bestFitness = -1, bestFitQ = 0, target = this.parseMediaRange(mimeType); |
| var targetType = target[0], targetSubtype = target[1], targetParams = target[2]; |
| |
| for (var i=0; i < parsedRanges.length; i++) { |
| var parsed = parsedRanges[i]; |
| var type = parsed[0], subtype = parsed[1], params = parsed[2]; |
| if ((type == targetType || type == "*" || targetType == "*") && |
| (subtype == targetSubtype || subtype == "*" || targetSubtype == "*")) { |
| var matchCount = 0; |
| for (var param in targetParams) { |
| if (param != 'q' && params[param] && params[param] == targetParams[param]) { |
| matchCount += 1; |
| } |
| } |
| |
| var fitness = (type == targetType) ? 100 : 0; |
| fitness += (subtype == targetSubtype) ? 10 : 0; |
| fitness += matchCount; |
| |
| if (fitness > bestFitness) { |
| bestFitness = fitness; |
| bestFitQ = params["q"]; |
| } |
| } |
| }; |
| return [bestFitness, parseFloat(bestFitQ)]; |
| }, |
| |
| // Find the best match for a given mime-type against |
| // a list of media_ranges that have already been |
| // parsed by parseMediaRange(). Returns the |
| // 'q' quality parameter of the best match, 0 if no |
| // match was found. This function bahaves the same as quality() |
| // except that 'parsedRanges' must be a list of |
| // parsed media ranges. |
| qualityParsed : function(mimeType, parsedRanges) { |
| return this.fitnessAndQualityParsed(mimeType, parsedRanges)[1]; |
| }, |
| |
| // Returns the quality 'q' of a mime-type when compared |
| // against the media-ranges in ranges. For example: |
| // |
| // >>> Mimeparse.quality('text/html','text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5') |
| // 0.7 |
| quality : function(mimeType, ranges) { |
| return this.qualityParsed(mimeType, parseRanges(ranges)); |
| }, |
| |
| // Takes a list of supported mime-types and finds the best |
| // match for all the media-ranges listed in header. The value of |
| // header must be a string that conforms to the format of the |
| // HTTP Accept: header. The value of 'supported' is a list of |
| // mime-types. |
| // |
| // >>> bestMatch(['application/xbel+xml', 'text/xml'], 'text/*;q=0.5,*/*; q=0.1') |
| // 'text/xml' |
| bestMatch : function(supported, header) { |
| var parsedHeader = parseRanges(header); |
| var weighted = []; |
| for (var i=0; i < supported.length; i++) { |
| weighted.push([publicMethods.fitnessAndQualityParsed(supported[i], parsedHeader), i, supported[i]]); |
| }; |
| weighted.sort(); |
| return weighted[weighted.length-1][0][1] ? weighted[weighted.length-1][2] : ''; |
| } |
| }; |
| return publicMethods; |
| })(); |