'use strict'; Ox.basename = function(string) { /* fixme: deprecate >>> Ox.basename("foo/bar/foo.bar") "foo.bar" >>> Ox.basename("foo.bar") "foo.bar" */ return string.replace(/^.*[\/\\]/g, ''); }; /*@ Ox.char Alias for String.fromCharCode @*/ Ox.char = String.fromCharCode; /*@ Ox.clean Remove leading, trailing and double whitespace from a string > Ox.clean("foo bar") "foo bar" > Ox.clean(" foo bar ") "foo bar" > Ox.clean(" foo \n bar ") "foo\nbar" > Ox.clean(" \nfoo\n\nbar\n ") "foo\nbar" > Ox.clean(" foo\tbar ") "foo bar" @*/ Ox.clean = function(string) { return Ox.filter(Ox.map(string.split('\n'), function(string) { return string.replace(/\s+/g, ' ').trim() || ''; })).join('\n'); }; /*@ Ox.endsWith Checks if a string ends with a given substring If the substring is a string literal (and not a variable), `/sub$/.test(str)` or `!!/sub$/.exec(str)` is shorter than `Ox.ends(str, sub)`. > Ox.endsWith('foobar', 'bar') true @*/ Ox.ends = Ox.endsWith = function(string, substring) { string = string.toString(); substring = substring.toString(); return string.slice(string.length - substring.length) == substring; }; /*@ Ox.isValidEmail Tests if a string is a valid e-mail address (str) -> True if the string is a valid e-mail address str Any string > Ox.isValidEmail("foo@bar.com") true > Ox.isValidEmail("foo.bar@foobar.co.uk") true > Ox.isValidEmail("foo@bar") false > Ox.isValidEmail("foo@bar..com") false @*/ Ox.isValidEmail = function(string) { return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6}$/i.test(string); }; /*@ Ox.pad Pad a string to a given length > Ox.pad(1, 2) "01" > Ox.pad("abc", -6, ".") "abc..." > Ox.pad("foobar", -3, ".") "foo" > Ox.pad("abc", -6, "123456") "abc123" > Ox.pad("abc", 6, "123456") "456abc" @*/ Ox.pad = function(string, length, padding) { // fixme: slighly obscure signature // fixme: weird for negative numbers var pos = length / (length = Math.abs(length)); string = string.toString().slice(0, length); padding = Ox.repeat(padding || '0', length - string.length); return pos == 1 ? (padding + string).slice(-length) : (string + padding).slice(0, length); }; /*@ Ox.parseDuration Takes a formatted duration, returns seconds > Ox.parseDuration('01:02:03') 3723 > Ox.parseDuration('3') 3 > Ox.parseDuration('2:') 120 > Ox.parseDuration('1::') 3600 @*/ Ox.parseDuration = function(string) { return string.split(':').reverse().slice(0, 3).reduce(function(p, c, i) { return p + (parseFloat(c) || 0) * Math.pow(60, i); }, 0); }; /*@ Ox.parsePath Returns the components of a path (str) -> Path extension File extension filename Filename pathname Pathname > Ox.parsePath('/foo/bar/foo.bar') {extension: 'bar', filename: 'foo.bar', pathname: '/foo/bar/'} > Ox.parsePath('foo/') {extension: '', filename: '', pathname: 'foo/'} > Ox.parsePath('foo') {extension: '', filename: 'foo', pathname: ''} > Ox.parsePath('.foo') {extension: '', filename: '.foo', pathname: ''} @*/ Ox.parsePath = function(string) { var matches = /^(.+\/)?(.+?(\..+)?)?$/.exec(string); return { pathname: matches[1] || '', filename: matches[2] || '', extension: matches[3] ? matches[3].slice(1) : '' }; } /*@ Ox.parseSRT Parses an srt subtitle file (str) -> Parsed subtitles in In point (sec) out Out point (sec) text Text str Contents of an srt subtitle file > Ox.parseSRT('1\n01:02:00,000 --> 01:02:03,400\nHello World') [{'in': 3720, out: 3723.4, text: 'Hello World'}] @*/ Ox.parseSRT = function(string, fps) { return string.replace(/\r\n/g, '\n').replace(/\n+$/, '').split('\n\n') .map(function(block) { var lines = block.split('\n'), points; lines.shift(); points = lines.shift().split(' --> ').map(function(point) { return point.replace(',', ':').split(':') .reduce(function(previous, current, index) { return previous + parseInt(current, 10) * [3600, 60, 1, 0.001][index]; }, 0); }); if (fps) { points = points.map(function(point) { return Math.round(point * fps) / fps; }); } return { 'in': points[0], out: points[1], text: lines.join('\n') }; }); }; /*@ Ox.parseURL Takes a URL, returns its components (url) -> URL components hash Hash host Host hostname Hostname origin Origin pathname Pathname port Port protocol Protocol search Search url URL > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').hash '#c' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').host 'www.foo.com:8080' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').hostname 'www.foo.com' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').origin 'http://www.foo.com:8080' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').pathname '/bar/index.html' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').port '8080' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').protocol 'http:' > Ox.parseURL('http://www.foo.com:8080/bar/index.html?a=0&b=1#c').search '?a=0&b=1' @*/ Ox.parseURL = (function() { var a = document.createElement('a'), keys = ['hash', 'host', 'hostname', 'origin', 'pathname', 'port', 'protocol', 'search']; return function(string) { var ret = {}; a.href = string; keys.forEach(function(key) { ret[key] = a[key]; }); return ret; }; }()); Ox.parseUserAgent = function(userAgent) { var aliases = { browser: { 'Firefox': /(Fennec|Firebird|Iceweasel|Minefield|Namoroka|Phoenix|SeaMonkey|Shiretoko)/ }, system: { 'BSD': /(FreeBSD|NetBSD|OpenBSD)/, 'Linux': /(CrOS|MeeGo|webOS)/, 'Unix': /(AIX|HP-UX|IRIX|SunOS)/ } }, names = { browser: { 'chromeframe': 'Chrome Frame', 'MSIE': 'Internet Explorer' }, system: { 'CPU OS': 'iOS', 'iPhone OS': 'iOS', 'Macintosh': 'Mac OS X' } }, regexps = { browser: [ /(Camino)\/(\d+)/, /(chromeframe)\/(\d+)/, /(Chrome)\/(\d+)/, /(Epiphany)\/(\d+)/, /(Firefox)\/(\d+)/, /(Galeon)\/(\d+)/, /(Googlebot)\/(\d+)/, /(Konqueror)\/(\d+)/, /(MSIE) (\d+)/, /(Netscape)\d?\/(\d+)/, /(NokiaBrowser)\/(\d+)/, /(Opera) (\d+)/, /(Opera)\/.+Version\/(\d+)/, /Version\/(\d+).+(Safari)/ ], system: [ /(Android) (\d+)/, /(BeOS)/, /(BlackBerry) (\d+)/, /(Darwin)/, /(BSD) (FreeBSD|NetBSD|OpenBSD)/, /(CPU OS) (\d+)/, /(iPhone OS) (\d+)/, /(Linux).+(CentOS|CrOS|Debian|Fedora|Gentoo|Mandriva|MeeGo|Mint|Red Hat|SUSE|Ubuntu|webOS)/, /(CentOS|CrOS|Debian|Fedora|Gentoo|Mandriva|MeeGo|Mint|Red Hat|SUSE|Ubuntu|webOS).+(Linux)/, /(Linux)/, /(Mac OS X) (10.\d)/, /(Mac OS X)/, /(Macintosh)/, /(SymbianOS)\/(\d+)/, /(SymbOS)/, /(OS\/2)/, /(Unix) (AIX|HP-UX|IRIX|SunOS)/, /(Unix)/, /(Windows) (NT \d\.\d)/, /(Windows) (95|98|2000|2003|ME|NT|XP)/, // Opera /(Windows).+(Win 9x 4\.90)/, // Firefox /(Windows).+(Win9\d)/, // Firefox /(Windows).+(WinNT4.0)/ // Firefox ] }, versions = { browser: {}, system: { '10.0': '10.0 (Cheetah)', '10.1': '10.1 (Puma)', '10.2': '10.2 (Jaguar)', '10.3': '10.3 (Panther)', '10.4': '10.4 (Tiger)', '10.5': '10.5 (Leopard)', '10.6': '10.6 (Snow Leopard)', '10.7': '10.7 (Lion)', '10.8': '10.8 (Mountain Lion)', 'CrOS': 'Chrome OS', 'NT 4.0': 'NT 4.0 (Windows NT)', 'NT 4.1': 'NT 4.1 (Windows 98)', 'Win 9x 4.90': 'NT 4.9 (Windows ME)', 'NT 5.0': 'NT 5.0 (Windows 2000)', 'NT 5.1': 'NT 5.1 (Windows XP)', 'NT 5.2': 'NT 5.2 (Windows 2003)', 'NT 6.0': 'NT 6.0 (Windows Vista)', 'NT 6.1': 'NT 6.1 (Windows 7)', 'NT 6.2': 'NT 6.2 (Windows 8)', '95': 'NT 4.0 (Windows 95)', 'NT': 'NT 4.0 (Windows NT)', '98': 'NT 4.1 (Windows 98)', 'ME': 'NT 4.9 (Windows ME)', '2000': 'NT 5.0 (Windows 2000)', '2003': 'NT 5.2 (Windows 2003)', 'XP': 'NT 5.1 (Windows XP)', 'Win95': 'NT 4.0 (Windows 95)', 'WinNT4.0': 'NT 4.0 (Windows NT)', 'Win98': 'NT 4.1 (Windows 98)' } }, userAgentData = {}; Ox.forEach(regexps, function(regexps, key) { userAgentData[key] = {name: '', string: '', version: ''}; Ox.forEach(aliases[key], function(regexp, alias) { userAgent = userAgent.replace( regexp, key == 'browser' ? alias : alias + ' $1' ); }); Ox.forEach(regexps, function(regexp) { var matches = userAgent.match(regexp), name, string, swap, version; if (matches) { matches[2] = matches[2] || ''; swap = matches[1].match(/^\d/) || matches[2] == 'Linux'; name = matches[swap ? 2 : 1]; version = matches[swap ? 1 : 2].replace('_', '.'); name = names[key][name] || name, version = versions[key][version] || version; string = name; if (version) { string += ' ' + ( ['BSD', 'Linux', 'Unix'].indexOf(name) > -1 ? '(' + version + ')' : version ) } userAgentData[key] = { name: names[name] || name, string: string, version: versions[version] || version }; Ox.Break(); } }); }); return userAgentData; }; /*@ Ox.repeat Repeat a value multiple times Works for arrays, numbers and strings > Ox.repeat(1, 3) "111" > Ox.repeat("foo", 3) "foofoofoo" > Ox.repeat([1, 2], 3) [1, 2, 1, 2, 1, 2] > Ox.repeat([{k: "v"}], 3) [{k: "v"}, {k: "v"}, {k: "v"}] @*/ Ox.repeat = function(value, times) { var ret; if (Ox.isArray(value)) { ret = []; Ox.loop(times, function() { ret = ret.concat(value); }); } else { ret = times >= 1 ? new Array(times + 1).join(value.toString()) : ''; } return ret; }; /*@ Ox.splice `[].splice` for strings, returns a new string > Ox.splice('12xxxxx89', 2, 5, 3, 4, 5, 6, 7) '123456789' @*/ Ox.splice = function(string, index, remove) { var array = string.split(''); Array.prototype.splice.apply(array, Ox.slice(arguments, 1)); return array.join(''); }; /*@ Ox.startsWith Checks if a string starts with a given substring If the substring is a string literal (and not a variable), `/^sub/.test(str)` or `!!/^sub/.exec(str)` is shorter than `Ox.starts(str, sub)`. > Ox.startsWith('foobar', 'foo') true @*/ Ox.starts = Ox.startsWith = function(string, substring) { // fixme: rename to starts string = string.toString(); substring = substring.toString(); return string.slice(0, substring.length) == substring; }; /*@ Ox.stripTags Strips HTML tags from a string > Ox.stripTags('foo') 'foo' @*/ Ox.stripTags = function(string) { return string.replace(/<.*?>/g, ''); }; /*@ Ox.toCamelCase Takes a string with '-', '/' or '_', returns a camelCase string > Ox.toCamelCase('foo-bar-baz') 'fooBarBaz' > Ox.toCamelCase('foo/bar/baz') 'fooBarBaz' > Ox.toCamelCase('foo_bar_baz') 'fooBarBaz' @*/ Ox.toCamelCase = function(string) { return string.replace(/[\-\/_][a-z]/g, function(string) { return string[1].toUpperCase(); }); }; /*@ Ox.toDashes Takes a camelCase string, returns a string with dashes > Ox.toDashes('fooBarBaz') 'foo-bar-baz' @*/ Ox.toDashes = function(string) { return string.replace(/[A-Z]/g, function(string) { return '-' + string.toLowerCase(); }); }; /*@ Ox.toSlashes Takes a camelCase string, returns a string with slashes > Ox.toSlashes('fooBarBaz') 'foo/bar/baz' @*/ Ox.toSlashes = function(string) { return string.replace(/[A-Z]/g, function(string) { return '/' + string.toLowerCase(); }); }; /*@ Ox.toTitleCase Returns a string with capitalized words > Ox.toTitleCase('foo') 'Foo' > Ox.toTitleCase('Apple releases iPhone, IBM stock plummets') 'Apple Releases iPhone, IBM Stock Plummets' @*/ Ox.toTitleCase = function(string) { return string.split(' ').map(function(value) { var substring = value.slice(1), lowercase = substring.toLowerCase(); if (substring == lowercase) { value = value.slice(0, 1).toUpperCase() + lowercase; } return value; }).join(' '); }; /*@ Ox.toUnderscores Takes a camelCase string, returns string with underscores > Ox.toUnderscores('fooBarBaz') 'foo_bar_baz' @*/ Ox.toUnderscores = function(string) { return string.replace(/[A-Z]/g, function(string) { return '_' + string.toLowerCase(); }); }; /*@ Ox.truncate Truncate a string to a given length (string[, position], length[, padding]) -> Truncated string > Ox.truncate('anticonstitutionellement', 16) 'anticonstitutio…' > Ox.truncate('anticonstitutionellement', 'left', 16) '…itutionellement' > Ox.truncate('anticonstitutionellement', 16, '...') 'anticonstitut...' > Ox.truncate('anticonstitutionellement', 'center', 16, '...') 'anticon...lement' @*/ Ox.truncate = function(string, position, length, padding) { var hasPosition = Ox.isString(arguments[1]), last = Ox.last(arguments); position = hasPosition ? arguments[1] : 'right'; length = hasPosition ? arguments[2] : arguments[1]; padding = Ox.isString(last) ? last : '…'; if (string.length > length) { if (position == 'left') { string = padding + string.slice(padding.length + string.length - length); } else if (position == 'center') { string = string.slice(0, Math.ceil((length - padding.length) / 2)) + padding + string.slice(-Math.floor((length - padding.length) / 2)); } else if (position == 'right') { string = string.slice(0, length - padding.length) + padding; } } return string; }; /*@ Ox.words Splits a string into words, removing punctuation (string) -> <[s]> Array of words string Any string > Ox.words('Let\'s "split" array-likes into key/value pairs--okay?') ["let's", "split", "array-likes", "into", "key", "value", "pairs", "okay"] @*/ Ox.words = function(string) { var array = string.toLowerCase().split(/\b/), length = array.length, startsWithWord = /\w/.test(array[0]); array.forEach(function(v, i) { // find single occurrences of "-" or "'" that are not at the beginning // or end of the string, and join the surrounding words with them if ( i > 0 && i < length - 1 && (v == '-' || v == '\'') ) { array[i + 1] = array[i - 1] + array[i] + array[i + 1]; array[i - 1] = array[i] = ''; } }); // remove elements that have been emptied above array = array.filter(function(v) { return v.length; }); // return words, not spaces or punctuation return array.filter(function(v, i) { return i % 2 == !startsWithWord; }); } /*@ Ox.wordwrap Wrap a string at word boundaries > Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, '
') "Anticonstitutionellement,
Paris s'eveille" > Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, '
') "Anticonstitution
ellement, Paris
s'eveille" > Ox.wordwrap('These are short words', 16, '
', true) 'These are
short words' @*/ Ox.wordwrap = function(str, len, sep, bal, spa) { // fixme: bad API, sep/bal/spa should be in options object var str = str === null ? '' : str.toString(), len = len || 80, sep = sep || '
', bal = bal || false, spa = Ox.isUndefined(spa) ? true : spa, words = str.split(' '), lines; if (bal) { // balance lines: test if same number of lines // can be achieved with a shorter line length lines = Ox.wordwrap(str, len, sep, false).split(sep); if (lines.length > 1) { // test shorter line, unless // that means cutting a word var max = Ox.max(words.map(function(word) { return word.length; })); while (len > max) { len--; if (Ox.wordwrap(str, len, sep, false).split(sep).length > lines.length) { len++; break; } } } } lines = ['']; words.forEach(function(word) { var chr; if ((lines[lines.length - 1] + word + ' ').length <= len + 1) { // word fits in current line lines[lines.length - 1] += word + ' '; } else { if (word.length <= len) { // word fits in next line lines.push(word + ' '); } else { // word is longer than line chr = len - lines[lines.length - 1].length; lines[lines.length - 1] += word.slice(0, chr); Ox.loop(chr, word.length, len, function(pos) { lines.push(word.substr(pos, len)); }); lines[lines.length - 1] += ' '; } } }); if (!spa) { lines = lines.map(function(line) { return line.trim(); }); } return lines.join(sep).trim(); };