Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster startup and mass lookups, returns real booleans #16

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 114 additions & 84 deletions browscap.js
Original file line number Diff line number Diff line change
@@ -1,94 +1,124 @@
var browsers = []
, inifile = './browscap.ini'
var inifile, browsers, ids, resultCache;

exports.setIni = function(filename) {
inifile = filename

// Re-parse if parsed before
if (browsers.length) {
browsers = parse(filename)
}
/**
* Initializes the library for using a specific .ini file.
*/
function init(filename) {
inifile = filename;
browsers = null;
ids = [];
resultCache = {};
}

function parse(filename) {
var current = {}
, browserArray = []
, patternIndex = []

require('fs')
.readFileSync(filename, 'ascii')
.split(/[\r\n]+/)
.forEach(function(line) {
// Skip comments and blank lines
if (/^\s*(;|$)/.test(line)) {
return
}

if (line[0] == '[') {
var pattern = line.slice(1, -1)

// Convert the pattern into a proper regex
current = {
__regex__: new RegExp('^'
+ pattern.replace(/\./g, '\\.')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/\//g, '\\/')
.replace(/\-/g, '\\-')
.replace(/\*/g, '.*')
.replace(/\?/g, '.?')
.replace(/\+/g, '\\+')
+ '$'),
__pattern__: pattern
}

browserArray.push(current) // Push new browser object onto array
patternIndex.push(pattern) // Push pattern onto pattern index
} else {
var parts = line.split('=')
, name = parts[0]
, value = parts[1]

if (value[0] == '"' && value.slice(-1) == '"') {
value = value.slice(1, -1)
}
/**
* Loads configured configuration file in browscap.ini format.
**/
function loadInifile() {
var pattern;

current[name] = value
}
})


//copy properties from parent definition, with up to depth=2 parents
for (var depth = 0; depth < 2; depth++) {
for (var i = 0; i < browserArray.length; i++) {
var current = browserArray[i]
if (current.hasOwnProperty('Parent')) {
var j = patternIndex.lastIndexOf(current['Parent'])
for (var key in browserArray[j]) {
if (key != '__regex__' && key != 'Parent' && typeof browserArray[i][key] == 'undefined') {
browserArray[i][key] = browserArray[j][key]
}
}
}
}
}
browsers = {};

require('fs')
.readFileSync(inifile, 'ascii')
.split(/[\r\n]+/)
.forEach(function(line) {
var parts, value;

// Skip comments and blank lines
if (/^\s*(;|$)/.test(line)) {
return;
}

browserArray.sort(function(a, b) {
return b.__pattern__.length - a.__pattern__.length;
});
if (line[0] === '[') {
pattern = line.slice(1, -1);
ids.push(pattern);

return browserArray
browsers[pattern] = {
regex: new RegExp('^'
+ pattern.replace(/\./g, '\\.')
.replace(/\(/g, '\\(')
.replace(/\)/g, '\\)')
.replace(/\//g, '\\/')
.replace(/\-/g, '\\-')
.replace(/\*/g, '.*')
.replace(/\?/g, '.?')
.replace(/\+/g, '\\+')
+ '$')
};
} else {
parts = line.split('=');
value = parts[1];

if (value[0] === '"' && value.slice(-1) === '"') {
value = value.slice(1, -1);
}

if (value === 'true' || value === 'false') {
// Convert string to boolean.
value = value === 'true';
}

browsers[pattern][parts[0]] = value;
}
});

// Sort browser ID's by descending length.
ids.sort(function(a, b) { return b.length - a.length; });
}

/**
* Adds properties not yet defined on a target object from a source object.
*/
function setProperties(target, source) {
Object.keys(source).forEach(function(key) {
if (!target.hasOwnProperty(key)) {
target[key] = source[key];
}
});
}

/**
* Sets configuration file.
*/
exports.setIni = function(filename) {
init(filename);
};

/**
* Returns browser information.
*/
exports.getBrowser = function(userAgent) {
if (!browsers.length) {
browsers = parse(inifile)
}

// Test user agent against each browser regex
for (var i = 0; i < browsers.length; i++) {
if (browsers[i].__regex__.test(userAgent)) {
return browsers[i]
var browser, parentId, parent, i;

if (browsers === null) {
loadInifile();
}
}
}

browser = resultCache.hasOwnProperty(userAgent) ? resultCache[userAgent] : false;

// Find first matching regex.
for (i = 0; !browser && i < ids.length; i++) {
if (browsers[ids[i]].regex.test(userAgent)) {
browser = {};
setProperties(browser, browsers[ids[i]]);

// Move up parent tree for more generic properties.
parentId = browser.Parent;

while (parentId) {
parent = browsers[ids[ids.indexOf(parentId)]];
setProperties(browser, parent);

// Next parent.
parentId = parent.Parent;
}

// Put result in cache.
resultCache[userAgent] = browser;
}
}

return browser;
};

init('./browscap.ini');
67 changes: 41 additions & 26 deletions test.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,47 @@
var browscap = require('./browscap');
//browscap.setIni('php_browscap.ini');
var browscap = require('./browscap'),
tests = [
{ ua: 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)',
browser: 'IE', version: '8.0'
},
{ ua: 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.13) Gecko/20100914 Firefox/3.5.13',
browser: 'Firefox', version: '3.5'
},
{ ua: 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10',
browser: 'Firefox', version: '3.6'
},
{ ua: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; WOW64; Trident/7.0; Touch; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; Tablet PC 2.0; IPH 1.1.21.4019; ASU2JS)',
browser: 'IE', version: '11.0'
},
{ ua: 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36',
browser: 'Chrome', version: '35.0'
},
{ ua: '-',
browser: 'Default Browser', version: '0.0'
},
],
passed = 0,
failed = 0,
i = 1,
test, browser, j;

console.log("Running tests...");
var passed = 0, failed = 0, i = 1;
console.log('Running tests...');

var tests = [
{ ua: 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)', browser: 'IE', version: '8.0' },
{ ua: 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.13) Gecko/20100914 Firefox/3.5.13', browser: 'Firefox', version: '3.5' },
{ ua: 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.10) Gecko/20100914 Firefox/3.6.10', browser: 'Firefox', version: '3.6' },
{ ua: 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; WOW64; Trident/7.0; Touch; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; Tablet PC 2.0; IPH 1.1.21.4019; ASU2JS)', browser: 'IE', version: '11.0' },
{ ua: 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36', browser: 'Chrome', version: '35.0' }
]


for (var j in tests) {
var test = tests[j];
var browser = browscap.getBrowser(test.ua);
if (browser['Browser'] == test['browser'] && browser['Version'] == test['version']) {
console.log("PASSED test " + i + ":");
passed++;
} else {
console.log("FAILED test " + i + " (expected " + test['browser'] + " " + test['version'] + "):");
failed++;
}
for (j in tests) {
test = tests[j];
browser = browscap.getBrowser(test.ua);

if (browser['Browser'] === test['browser'] && browser['Version'] === test['version']) {
console.log("PASSED test " + i + ":");
passed++;
} else {
console.log("FAILED test " + i + " (expected " + test['browser'] + " " + test['version'] + "):");
failed++;
}

i++;

console.log(browser);
console.log("\n=====\n");
console.log('\n=====\n');
}

console.log("Passed " + passed + "/" + (passed + failed) + " tests");

console.log('Passed ' + passed + '/' + (passed + failed) + ' tests');