'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) { // fixme: rename to ends string = string.toString(); substring = substring.toString(); return string.slice(string.length - substring.length) == substring; }; /*@ Ox.highlight Highlight matches in a string > Ox.highlight('foobar', 'foo', 'match') 'foobar' @*/ // fixme: with regexp, special chars have to be escaped Ox.highlight = function(txt, str, classname) { return str && str.length ? txt.replace( new RegExp('(' + str + ')', 'ig'), '$1' ) : txt; }; /*@ Ox.highlightHTML Highlight matches in an HTML string > Ox.highlightHTML('foobar', 'foobar', 'h') 'foobar' > Ox.highlightHTML('foobar', 'foobar', 'h') 'foobar' > Ox.highlightHTML('foo
bar', 'foobar', 'h') 'foo
bar' > Ox.highlightHTML('AT&T', 'AT&T', 'h') 'AT&T' > Ox.highlightHTML('AT&T', 'amp', 'h') 'AT&T' > Ox.highlightHTML('a <b> c', '', 'h') 'a <b> c' > Ox.highlightHTML('a
c', 'b', 'h') 'a
c' @*/ Ox.highlightHTML = function(html, string, classname, tags) { var count = 0, isEntity = false, isTag = false, position, positions = []; //fixme: default tags should be same as in parseHTML tags = (tags || []).concat([ // inline formatting 'b', 'code', 'i', 's', 'sub', 'sup', 'u', // block formatting 'blockquote', 'h1', 'h2', 'h3', 'p', 'pre', // lists 'li', 'ol', 'ul', // tables 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', // other 'a', 'br', 'img', ]); string = Ox.encodeHTML(string).toLowerCase(); Ox.forEach(html.toLowerCase(), function(char, i) { // check for entity or tag start if (!isEntity && char == '&') { isEntity = true; } else if (!isTag && char == '<') { Ox.forEach(tags, function(tag) { if (html.slice(i + 1).match(new RegExp('^/?' + tag + '\\W'))) { isTag = true; Ox.Break(); } }); } // if outside entity or tag if (!isEntity && !isTag) { // if character matches if (char == string[count]) { if (count == 0) { position = i; } count++; if (count == string.length) { // make sure matches are last to first positions.unshift([position, i + 1]); } } else { count = 0; } } // check for entity or tag end if (isEntity && char == ';') { isEntity = false; } else if (isTag && char == '>') { isTag = false; } }); positions.forEach(function(position) { var match = '' + html.slice(position[0], position[1]) .replace(/(<.*?>)/g, '$1') + ''; html = html.slice(0, position[0]) + match + html.slice(position[1]); }); return html; } /*@ 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.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.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.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, length) Truncated string (string, length, position) -> Truncated string (string, length, placeholder) -> Truncated string (string, length, position, placeholder) -> Truncated string > Ox.truncate('anticonstitutionellement', 16) 'anticonstitut...' > Ox.truncate('anticonstitutionellement', 16, '...', 'left') '...utionellement' > Ox.truncate('anticonstitutionellement', 16, '>') 'anticonstitutio>' > Ox.truncate('anticonstitutionellement', 16, '...', 'center') 'anticon...lement' @*/ Ox.truncate = function(string, length, padding, position) { padding = padding || '...'; position = position || 'right'; 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(); };