diff --git a/index.js b/index.js index 1d520ad..c6ca2a0 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,10 @@ var agentparsers = regexps.browser var deviceparsers = regexps.device , deviceparserslength = deviceparsers.length; +// Default for unknown version +var UnknownFamily = 'Other'; +var UnknownVersion = '0'; + /** * The representation of a parsed user agent. * @@ -32,13 +36,29 @@ var deviceparsers = regexps.device * @api public */ function Agent(family, major, minor, patch, source) { - this.family = family || 'Other'; - this.major = major || '0'; - this.minor = minor || '0'; - this.patch = patch || '0'; + this.family = family || UnknownFamily; + this.major = major || UnknownVersion; + this.minor = minor || UnknownVersion; + this.patch = patch || UnknownVersion; this.source = source || ''; } +function replaceRegExResult(matches, templates) { + var r = []; + var m = matches.map(function(v){ v = typeof v === 'string' ? v.replace(/\&/g, '&&').replace(/\$/g, '&D') : v; }); + for(var t=1; t<=4; t++) { // template[1..4] + var tmpl = templates[t]; + r[t] = tmpl[0]; + if (typeof r[t] === 'string') { + tmpl[1].forEach(function(i){ + r[t] = r[t].replace(new RegExp('\\$'+i, 'g'), matches[i] != undefined ? matches[i] : ''); + }); + r[t] = r[t].replace(/&D/g, '$').replace(/&&/g, '&'); + } + } + return r; +} + /** * OnDemand parsing of the Operating System. * @@ -58,7 +78,7 @@ Object.defineProperty(Agent.prototype, 'os', { if (res = parsers[i][0].exec(userAgent)) { parser = parsers[i]; - if (parser[1]) res[1] = parser[1].replace('$1', res[1]); + res = replaceRegExResult(res, parser); break; } } @@ -68,9 +88,9 @@ Object.defineProperty(Agent.prototype, 'os', { ? new OperatingSystem() : new OperatingSystem( res[1] - , parser[2] || res[2] - , parser[3] || res[3] - , parser[4] || res[4] + , res[2] + , res[3] + , res[4] ) }).os; }, @@ -109,7 +129,7 @@ Object.defineProperty(Agent.prototype, 'device', { if (res = parsers[i][0].exec(userAgent)) { parser = parsers[i]; - if (parser[1]) res[1] = parser[1].replace('$1', res[1]); + res = replaceRegExResult(res, parser); break; } } @@ -119,9 +139,9 @@ Object.defineProperty(Agent.prototype, 'device', { ? new Device() : new Device( res[1] - , parser[2] || res[2] - , parser[3] || res[3] - , parser[4] || res[4] + , res[2] + , res[3] + , res[4] ) }).device; }, @@ -161,7 +181,7 @@ Agent.prototype.toAgent = function toAgent() { */ Agent.prototype.toString = function toString() { var agent = this.toAgent() - , os = this.os !== 'Other' ? this.os : false; + , os = this.os !== UnknownFamily ? this.os : false; return agent + (os ? ' / ' + os : ''); }; @@ -220,10 +240,10 @@ Agent.prototype.toJSON = function toJSON() { * @api public */ function OperatingSystem(family, major, minor, patch) { - this.family = family || 'Other'; - this.major = major || '0'; - this.minor = minor || '0'; - this.patch = patch || '0'; + this.family = family || UnknownFamily; + this.major = major || UnknownVersion; + this.minor = minor || UnknownVersion; + this.patch = patch || UnknownVersion; } /** @@ -293,10 +313,10 @@ OperatingSystem.prototype.toJSON = function toJSON(){ * @api public */ function Device(family, major, minor, patch) { - this.family = family || 'Other'; - this.major = major || '0'; - this.minor = minor || '0'; - this.patch = patch || '0'; + this.family = family || UnknownFamily; + this.major = major || UnknownVersion; + this.minor = minor || UnknownVersion; + this.patch = patch || UnknownVersion; } /** @@ -427,12 +447,12 @@ exports.parse = function parse(userAgent, jsAgent) { if (res = parsers[i][0].exec(userAgent)) { parser = parsers[i]; - if (parser[1]) res[1] = parser[1].replace('$1', res[1]); + res = replaceRegExResult(res, parser); if (!jsAgent) return new Agent( res[1] - , parser[2] || res[2] - , parser[3] || res[3] - , parser[4] || res[4] + , res[2] + , res[3] + , res[4] , userAgent ); @@ -584,6 +604,15 @@ exports.fromJSON = function fromJSON(details) { return agent; }; + +exports.setUnknownFamilyString = function setUnknownFamilyString(s) { + UnknownFamily = s; +} + +exports.setUnknownVersionString = function setUnknownVersionString(s) { + UnknownVersion = s; +} + /** * Library version. * diff --git a/lib/update.js b/lib/update.js index e18ca9d..8ab3fb5 100644 --- a/lib/update.js +++ b/lib/update.js @@ -50,6 +50,35 @@ exports.update = function update(callback) { }); }; +function getReplacement(field, resource, fallback, lastRepl) { + lastRepl = lastRepl || {}; + var usedMap = lastRepl.usedMap || {}; + + var used = []; + if (usedMap[fallback]) { + fallback = ''; + } else { + used = [fallback.replace('$', '')]; + } + + if (! field) + return { text: fallback, used: used, usedMap: usedMap }; + if (Array.isArray(field)) + field = field.filter(function(f){ return resource[f] })[0]; + if (! field) + return { text: fallback, used: used, usedMap: usedMap }; + if (resource[field] == undefined) + return { text: fallback, used: used, usedMap: usedMap }; + + var t = resource[field]; + var d = {}; + var m = t.match(/\$\d/g); + if (m) m.forEach(function(m) { usedMap[m] = true; m = m.replace('$',''); d[m] = true; }); + used = Object.keys(d).sort(function(a,b){ return a-b; }); + + return { text: t, used: used, usedMap: usedMap }; +} + /** * Parse the given sources. * @@ -93,17 +122,17 @@ exports.parse = function parse(sources, callback) { [ { resource: 'user_agent_parsers' - , replacement: 'family_replacement' + , replacement: ['family_replacement', 'v1_replacement', 'v2_replacement', 'v3_replacement' ] , name: 'browser' } , { resource: 'device_parsers' - , replacement: 'device_replacement' + , replacement: [['brand_replacement', 'device_replacement' ], 'model_replacement'] // , name: 'device' } , { resource: 'os_parsers' - , replacement: 'os_replacement' + , replacement: ['os_replacement', 'os_v1_replacement', 'os_v2_replacement' ] , name: 'os' } ].forEach(function parsing(details) { @@ -120,41 +149,21 @@ exports.parse = function parse(sources, callback) { // We need to JSON stringify the data to properly add slashes escape other // kinds of crap in the RegularExpression. If we don't do thing we get // some illegal token warnings. + var rflags = resource.regex_flag || ''; + if (rflags) rflags = ", '" + rflags + "'"; parser = 'parser = Object.create(null);\n'; - parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + ');\n'; + parser += 'parser[0] = new RegExp('+ JSON.stringify(resource.regex) + rflags + ');\n'; // Check if we have replacement for the parsed family name - if (resource[details.replacement]) { - parser += 'parser[1] = "'+ resource[details.replacement].replace('"', '\\"') +'";'; - } else { - parser += 'parser[1] = 0;'; - } - - parser += '\n'; - - if (resource.v1_replacement) { - parser += 'parser[2] = "'+ resource.v1_replacement.replace('"', '\\"') +'";'; - } else { - parser += 'parser[2] = 0;'; - } - - parser += '\n'; - - if (resource.v2_replacement) { - parser += 'parser[3] = "'+ resource.v2_replacement.replace('"', '\\"') +'";'; - } else { - parser += 'parser[3] = 0;'; - } - - parser += '\n'; + var r = {}; + for(var j = 0; j < 4; j++) { + r = getReplacement(details.replacement[j], resource, '$'+(j+1), r); + var used = r.used.join(', '); + parser += 'parser[' + (j+1) + '] = ["'+ r.text.replace('"', '\\"') +'", [' + used + ']];'; - if (resource.v3_replacement) { - parser += 'parser[4] = "'+ resource.v3_replacement.replace('"', '\\"') +'";'; - } else { - parser += 'parser[4] = 0;'; + parser += '\n'; } - parser += '\n'; parser += 'exports.'+ details.name +'['+ i +'] = parser;'; results[details.resource].push(parser); }