From 5717b00343e539f957088095dae7920b1e0f3caa Mon Sep 17 00:00:00 2001 From: Faisal Salman Date: Mon, 18 Mar 2013 18:30:05 +0700 Subject: [PATCH] Some premature optimizations --- src/ua-parser.js | 169 ++++++++++++++++++++++++++--------------------- 1 file changed, 94 insertions(+), 75 deletions(-) diff --git a/src/ua-parser.js b/src/ua-parser.js index c36cd54..225865d 100644 --- a/src/ua-parser.js +++ b/src/ua-parser.js @@ -1,4 +1,4 @@ -// UAParser.js v0.5.20 +// UAParser.js v0.5.21 // Lightweight JavaScript-based User-Agent string parser // https://github.com/faisalman/ua-parser-js // @@ -15,9 +15,10 @@ var EMPTY = '', - FUNC = 'function', - UNDEF = 'undefined', - OBJ = 'object', + UNKNOWN = '?', + FUNC_TYPE = 'function', + UNDEF_TYPE = 'undefined', + OBJ_TYPE = 'object', MAJOR = 'major', MODEL = 'model', NAME = 'name', @@ -29,6 +30,21 @@ TABLET = 'tablet'; + /////////// + // Helper + ////////// + + + var util = { + has : function (str1, str2) { + return str2.toLowerCase().indexOf(str1.toLowerCase()) !== -1; + }, + isType : function (obj, str) { + return typeof obj === str; + } + }; + + /////////////// // Map helper ////////////// @@ -36,71 +52,72 @@ var mapper = { - regex : function () { - - var result, i, j, k, l, m, args = arguments; + rgx : function () { // loop through all regexes maps - for (i = 0; i < args.length; i += 2) { + for (var result, i = 0, j, k, p, matches, match, args = arguments; i < args.length; i += 2) { var regex = args[i], // even sequence (0,2,4,..) props = args[i + 1]; // odd sequence (1,3,5,..) // construct object barebones - if (typeof result === UNDEF) { + if (util.isType(result, UNDEF_TYPE)) { result = {}; - for (k = 0; k < props.length; k++) { - if (typeof props[k] === OBJ) { - result[props[k][0]] = undefined; + for (p in props) { + if (util.isType(p, OBJ_TYPE)) { + result[p[0]] = undefined; } else { - result[props[k]] = undefined; + result[p] = undefined; } } - if (this.getUA().toString() === EMPTY) { - return result; - } } // try matching uastring with regexes - for (j = 0; j < regex.length; j++) { - l = regex[j].exec(this.getUA()); - if (!!l) { - for (k = 0; k < props.length; k++) { - m = l[k + 1]; - if (typeof props[k] === OBJ && props[k].length === 2) { - result[props[k][0]] = props[k][1]; - } else if (typeof props[k] === OBJ && props[k].length === 3) { - if (typeof props[k][1] === FUNC && !(props[k][1].exec && props[k][1].test)) { - result[props[k][0]] = m ? props[k][1].call(this, m, props[k][2]) : undefined; - } else { - result[props[k][0]] = m ? m.replace(props[k][1], props[k][2]) : undefined; + for (j = k = 0; j < regex.length; j++) { + matches = regex[j].exec(this.getUA()); + if (!!matches) { + for (p in props) { + match = matches[k + 1]; + // check if given property is actually array + if (util.isType(p, OBJ_TYPE)) { + if (p.length == 2) { + // assign given value, ignore regex match + result[p[0]] = p[1]; + } else if (p.length == 3) { + // check whether function or regex + if (util.isType(p[1], FUNC_TYPE) && !(p[1].exec && p[1].test)) { + // call function (usually string mapper) + result[p[0]] = match ? p[1].call(this, match, p[2]) : undefined; + } else { + // sanitize match using given regex + result[p[0]] = match ? match.replace(p[1], p[2]) : undefined; + } } } else { - result[props[k]] = m ? m : undefined; + result[p] = match ? match : undefined; } } break; } } - if(!!l) break; // break the loop immediately if match found + if(!!matches) break; // break the loop immediately if match found } return result; }, - string : function (str, map) { + str : function (str, map) { for (var i in map) { - if (map.hasOwnProperty(i)) { - if (typeof map[i] === OBJ && map[i].length > 0) { - for (var j = 0; j < map[i].length; j++) { - if (str.toLowerCase().indexOf(map[i][j].toLowerCase()) !== -1) { - return (i.toString() === UNDEF) ? undefined : i; - } + // check if array + if (util.isType(map[i], OBJ_TYPE) && map[i].length > 0) { + for (var j in map[i]) { + if (util.has(j, str)) { + return (i === UNKNOWN) ? undefined : i; } - } else if (str.toLowerCase().indexOf(map[i].toLowerCase()) !== -1) { - return (i.toString() === UNDEF) ? undefined : i; } + } else if (util.has(map[i], str)) { + return (i === UNKNOWN) ? undefined : i; } } return str; @@ -118,19 +135,19 @@ browser : { oldsafari : { major : { - '1' : ['/85', '/125', '/312'], - '2' : ['/412', '/416', '/417', '/419'], - 'undefined' : '/' + '1' : ['/8', '/1', '/3'], + '2' : '/4', + '?' : '/' }, version : { - '1.0' : '/85', - '1.2' : '/125', - '1.3' : '/312', + '1.0' : '/8', + '1.2' : '/1', + '1.3' : '/3', '2.0' : '/412', '2.0.2' : '/416', '2.0.3' : '/417', '2.0.4' : '/419', - 'undefined' : '/' + '?' : '/' } } }, @@ -191,7 +208,7 @@ // Lunascape/Maxthon/Netfront/Jasmine/Blazer // Trident based - /(avant\sbrowser|iemobile|slimbrowser|baidubrowser)[\/\s]?((\d+)?[\w\.]*)/i, + /(avant\s|iemobile|slim|baidu)(?:browser)?[\/\s]?((\d+)?[\w\.]*)/i, // Avant/IEMobile/SlimBrowser/Baidu /ms(ie)\s((\d+)?[\w\.]+)/i, // Internet Explorer @@ -223,11 +240,11 @@ /version\/((\d+)?[\w\.]+).+?(mobile\s?safari|safari)/i // Safari & Safari Mobile ], [VERSION, MAJOR, NAME], [ - /applewebkit.+?(mobile\s?safari|safari)((\/[\w\.]+))/i // Safari < 3.0 - ], [NAME, [MAJOR, mapper.string, maps.browser.oldsafari.major], [VERSION, mapper.string, maps.browser.oldsafari.version]], [ + /webkit.+?(mobile\s?safari|safari)((\/[\w\.]+))/i // Safari < 3.0 + ], [NAME, [MAJOR, mapper.str, maps.browser.oldsafari.major], [VERSION, mapper.str, maps.browser.oldsafari.version]], [ /(konqueror)\/((\d+)?[\w\.]+)/i, // Konqueror - /(applewebkit|khtml)\/((\d+)?[\w\.]+)/i + /(webkit|khtml)\/((\d+)?[\w\.]+)/i ], [NAME, VERSION, MAJOR], [ // Gecko based @@ -279,11 +296,14 @@ /(sony)\s(tablet\s[ps])/i // Sony Tablets ], [VENDOR, MODEL, [TYPE, TABLET]], [ - /(nintendo|playstation)\s([wids3portablev]+)/i // Nintendo/Playstation + /(nintendo)\s([wids3u]+)/i // Nintendo ], [VENDOR, MODEL, [TYPE, CONSOLE]], [ + /((playstation)\s[3portablevi]+)/i // Playstation + ], [[VENDOR, 'Sony'], MODEL, [TYPE, CONSOLE]], [ + /(sprint\s(\w+))/i // Sprint Phones - ], [[VENDOR, mapper.string, maps.device.sprint.vendor], [MODEL, mapper.string, maps.device.sprint.model], [TYPE, MOBILE]], [ + ], [[VENDOR, mapper.str, maps.device.sprint.vendor], [MODEL, mapper.str, maps.device.sprint.model], [TYPE, MOBILE]], [ /(htc)[;_\s-]+([\w\s]+(?=\))|\w+)*/i, // HTC /(zte)-(\w+)*/i, // ZTE @@ -326,10 +346,8 @@ /(presto)\/([\w\.]+)/i, // Presto /(webkit|trident|netfront|netsurf|amaya|lynx|w3m)\/([\w\.]+)/i, // WebKit/Trident/NetFront/NetSurf/Amaya/Lynx/w3m - /(khtml)\/([\w\.]+)/i, // KHTML - /(tasman)\s([\w\.]+)/i, // Tasman - /(links)\s\(([\w\.]+)/i, // Links - /(icab)[\/\s]([2-3]\.[\d\.]+)/i // iCab + /(khtml|tasman|links)[\/\s]\(?([\w\.]+)/i, // KHTML/Tasman/Links + /(icab)[\/\s]([23]\.[\d\.]+)/i // iCab ], [NAME, VERSION], [ /rv\:([\w\.]+).*(gecko)/i // Gecko @@ -341,9 +359,9 @@ // Windows based /(windows)\snt\s6\.2;\s(arm)/i, // Windows RT /(windows\sphone(?:\sos)*|windows\smobile|windows)[\s\/]?([ntce\d\.\s]+\w)/i - ], [NAME, [VERSION, mapper.string, maps.os.windows.version]], [ + ], [NAME, [VERSION, mapper.str, maps.os.windows.version]], [ /(win(?=3|9|n)|win\s9x\s)([nt\d\.]+)/i - ], [[NAME, 'Windows'], [VERSION, mapper.string, maps.os.windows.version]], [ + ], [[NAME, 'Windows'], [VERSION, mapper.str, maps.os.windows.version]], [ // Mobile/Embedded OS /\((bb)(10);/i // BlackBerry 10 @@ -359,7 +377,7 @@ ], [[NAME, 'Firefox OS'], VERSION], [ // Console - /(nintendo|playstation)\s([wids3portablev]+)/i, // Nintendo/Playstation + /(nintendo|playstation)\s([wids3portablevu]+)/i, // Nintendo/Playstation // GNU/Linux based /(mint)[\/\s\(]?(\w+)*/i, // Mint @@ -378,7 +396,7 @@ ], [[NAME, 'Solaris'], VERSION], [ // BSD based - /\s(\w*bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/DragonFly + /\s([frentopc-]{0,4}bsd|dragonfly)\s?([\w\.]+)*/i // FreeBSD/NetBSD/OpenBSD/PC-BSD/DragonFly ], [NAME, VERSION],[ /(ip[honead]+)(?:.*os\s*([\w]+)*\slike\smac|;\sopera)/i // iOS @@ -403,7 +421,7 @@ //////////////// - var UAParser = function UAParser (uastring) { + var UAParser = function (uastring) { var ua = uastring || ((window && window.navigator && window.navigator.userAgent) ? window.navigator.userAgent : EMPTY); @@ -411,16 +429,16 @@ return new UAParser(uastring).getResult(); } this.getBrowser = function () { - return mapper.regex.apply(this, regexes.browser); + return mapper.rgx.apply(this, regexes.browser); }; this.getDevice = function () { - return mapper.regex.apply(this, regexes.device); + return mapper.rgx.apply(this, regexes.device); }; this.getEngine = function () { - return mapper.regex.apply(this, regexes.engine); + return mapper.rgx.apply(this, regexes.engine); }; this.getOS = function () { - return mapper.regex.apply(this, regexes.os); + return mapper.rgx.apply(this, regexes.os); }; this.getResult = function() { return { @@ -447,13 +465,13 @@ // check js environment - if (typeof exports !== UNDEF && !/\[object\s[DOM]*Window\]/.test(global.toString())) { + if (!util.isType(exports, UNDEF_TYPE)) { // nodejs env - if (typeof module !== UNDEF && module.exports) { + if (!util.isType(module, UNDEF_TYPE) && module.exports) { exports = module.exports = UAParser; } exports.UAParser = UAParser; - } else if (typeof define === FUNC && define.amd) { + } else if (util.isType(define, FUNC_TYPE) && define.amd) { // requirejs env define(function() { return UAParser; @@ -461,21 +479,22 @@ } else { // browser env window.UAParser = UAParser; - // jQuery specific - if (typeof window.jQuery !== UNDEF) { + // jQuery specific (optional) + if (!util.isType(window.jQuery, UNDEF_TYPE)) { + var $ = window.jQuery; var parser = new UAParser(); - window.jQuery.ua = parser.getResult(); - window.jQuery.ua.get = function() { + $.ua = parser.getResult(); + $.ua.get = function() { return parser.getUA(); }; - window.jQuery.ua.set = function(uastring) { + $.ua.set = function(uastring) { parser.setUA(uastring); var result = parser.getResult(); for (var prop in result) { - window.jQuery.ua[prop] = result[prop]; + $.ua[prop] = result[prop]; } }; } } -})(this); +})(this); \ No newline at end of file