'use strict'; Ox.basename = function(str) { /* fixme: deprecate >>> Ox.basename("foo/bar/foo.bar") "foo.bar" >>> Ox.basename("foo.bar") "foo.bar" */ return str.replace(/^.*[\/\\]/g, ''); }; /*@ Ox.char Alias for String.fromCharCode @*/ // fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])? 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(str) { return Ox.map(str.split('\n'), function(str) { return Ox.trim(str.replace(/\s+/g, ' ')) || null; }).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(str, sub) { // fixme: rename to ends return str.substr(str.length - sub.length) == sub; }; /*@ 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, str, classname, tags) { var count = 0, isEntity = false, isTag = false, position, positions = []; //fixme: default tags should be same as in parseHTML tags = Ox.merge(tags || [], [ // 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', ]); str = Ox.encodeHTML(str).toLowerCase(); Ox.forEach(html.toLowerCase(), function(chr, i) { // check for entity or tag start if (!isEntity && chr == '&') { isEntity = true; } else if (!isTag && chr == '<') { Ox.forEach(tags, function(tag) { if (html.substr(i + 1).match(new RegExp('^/?' + tag + '\\W'))) { isTag = true; return false; } }); } // if outside entity or tag if (!isEntity && !isTag) { // if character matches if (chr == str[count]) { if (count == 0) { position = i; } count++; if (count == str.length) { // make sure matches are last to first positions.unshift([position, i + 1]); } } else { count = 0; } } // check for entity or tag end if (isEntity && chr == ';') { isEntity = false; } else if (isTag && chr == '>') { isTag = false; } }); positions.forEach(function(position) { var match = '' + html.substr(position[0], position[1] - position[0]) .replace(/(<.*?>)/g, '$1') + ''; html = html.substr(0, position[0]) + match + html.substr(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(str) { return !!/^[0-9A-Z\.\+\-_]+@(?:[0-9A-Z\-]+\.)+[A-Z]{2,6}$/i.test(str); }; /*@ 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(str, len, pad) { // fixme: slighly obscure signature // fixme: weird for negative numbers var pos = len / (len = Math.abs(len)); str = str.toString().substr(0, len); pad = Ox.repeat(pad || '0', len - str.length); str = pos == 1 ? pad + str : str + pad; str = pos == 1 ? str.substr(str.length - len, str.length) : str.substr(0, len); return str; }; /*@ 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(str) { var matches = /^(.+\/)?(.+?(\..+)?)?$/.exec(str); return { pathname: matches[1] || '', filename: matches[2] || '', extension: matches[3] ? matches[3].substr(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(str, fps) { return str.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(prev, curr, i) { return prev + parseInt(curr, 10) * [3600, 60, 1, 0.001][i]; }, 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(str) { var names = { chromeframe: 'Internet Explorer (Chrome Frame)', 'iPhone OS': 'iOS', Fennec: 'Mobile Firefox', Mobile: 'Mobile Safari', MSIE: 'Internet Explorer', }, regexps = { browser: [ /(chromeframe)\/(\d+)/, /(Chrome)\/(\d+)/, /(Fennec)\/(\d+)/, /(Firefox)\/(\d+)/, /(MSIE)\/(\d+)/, /(Opera)\/.+Version\/(\d+)/, /Version\/(\d+).+(Mobile)\/.+Safari/, /Version\/(\d+).+(Safari)/ ], system: [ /(iPhone OS) (\d+)/, / (Linux) /, /(Mac OS X) (10.\d)/, /(Windows) (NT \d\.\d)/ ] }, userAgent = { browser: {name: '', version: ''}, system: {name: '', version: ''} }, versions = { '10.3': 'Panther', '10.4': 'Tiger', '10.5': 'Leopard', '10.6': 'Snow Leopard', '10.7': 'Lion', 'NT 5.0': '2000', 'NT 5.1': 'XP', 'NT 5.2': '2003', 'NT 6.0': 'Vista', 'NT 6.1': '7' } Ox.forEach(regexps, function(regexps, key) { regexps.forEach(function(regexp) { var matches = str.match(regexp), name, swap, version; if (matches) { matches[2] = matches[2] || ''; swap = matches[1].match(/^\d+$/); name = matches[swap ? 2 : 1]; version = matches[swap ? 1 : 2].replace('_', '.'); userAgent[key] = { name: names[name] || name, version: versions[version] || version }; return false; } }); }); return userAgent; }; /*@ 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(val, num) { var ret; if (Ox.isArray(val)) { ret = []; num >= 1 && Ox.loop(num, function() { ret = Ox.merge(ret, val); }); } else { ret = num >= 1 ? new Array(num + 1).join(val.toString()) : ''; } return ret; }; /*@ Ox.reverse Reverses a string > Ox.reverse('foobar') 'raboof' @*/ Ox.reverse = function(str) { return str.toString().split('').reverse().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(str, sub) { // fixme: rename to starts return str.substr(0, sub.length) == sub; }; /*@ Ox.stripTags Strips HTML tags from a string > Ox.stripTags('foo') 'foo' @*/ Ox.stripTags = function(str) { return str.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(str) { return str.replace(/[\-\/_][a-z]/g, function(str) { return str[1].toUpperCase(); }); }; /*@ Ox.toDashes Takes a camelCase string, returns a string with dashes > Ox.toDashes('fooBarBaz') 'foo-bar-baz' @*/ Ox.toDashes = function(str) { return str.replace(/[A-Z]/g, function(str) { return '-' + str.toLowerCase(); }); }; /*@ Ox.toSlashes Takes a camelCase string, returns a string with slashes > Ox.toSlashes('fooBarBaz') 'foo/bar/baz' @*/ Ox.toSlashes = function(str) { /* */ return str.replace(/[A-Z]/g, function(str) { return '/' + str.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(str) { return Ox.map(str.split(' '), function(val) { var sub = val.substr(1), low = sub.toLowerCase(); if (sub == low) { val = val.substr(0, 1).toUpperCase() + low; } return val; }).join(' '); }; /*@ Ox.toUnderscores Takes a camelCase string, returns string with underscores > Ox.toUnderscores('fooBarBaz') 'foo_bar_baz' @*/ Ox.toUnderscores = function(str) { return str.replace(/[A-Z]/g, function(str) { return '_' + str.toLowerCase(); }); }; Ox.trim = function(str) { // is in jQuery, and in JavaScript itself /* Ox.trim(" foo ") "foo" */ return str.replace(/^\s+|\s+$/g, ''); }; /*@ 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(str, len, pad, pos) { var pad = pad || '...', pos = pos || 'right', strlen = str.length, padlen = pad.length, left, right; if (strlen > len) { if (pos == 'left') { str = pad + str.substr(padlen + strlen - len); } else if (pos == 'center') { left = Math.ceil((len - padlen) / 2); right = Math.floor((len - padlen) / 2); str = str.substr(0, left) + pad + str.substr(-right); } else if (pos == 'right') { str = str.substr(0, len - padlen) + pad; } } return str; }; /*@ 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(str) { var arr = str.toLowerCase().split(/\b/), chr = "-'", len = arr.length, startsWithWord = /\w/.test(arr[0]); arr.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 < len - 1 && v.length == 1 && chr.indexOf(v) > -1 ) { arr[i + 1] = arr[i - 1] + arr[i] + arr[i + 1]; arr[i - 1] = arr[i] = ''; } }); // remove elements that have been emptied above arr = arr.filter(function(v) { return v.length; }); // return words, not spaces or punctuation return arr.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(Ox.map(words, function(word) { return word.length; })); while (len > max) { len--; if (Ox.wordwrap(str, len, sep, false).split(sep).length > lines.length) { len++; break; } } } } lines = ['']; Ox.forEach(words, function(word) { 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 var chr = len - lines[lines.length - 1].length; lines[lines.length - 1] += word.substr(0, chr); for (var pos = chr; pos < word.length; pos += len) { lines.push(word.substr(pos, len)); } lines[lines.length - 1] += ' '; } } }); if (!spa) { lines = Ox.map(lines, function(line) { return Ox.trim(line); }); } return Ox.trim(lines.join(sep)); };