559 lines
17 KiB
JavaScript
559 lines
17 KiB
JavaScript
'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 <f> Alias for String.fromCharCode
|
|
@*/
|
|
// fixme: add some mapping? like Ox.char(9, 13) or Ox.char([9, 13])?
|
|
Ox.char = String.fromCharCode;
|
|
|
|
/*@
|
|
Ox.clean <f> 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 <f> Checks if a string ends with a given substring
|
|
If the substring is a string literal (and not a variable),
|
|
<code>/sub$/.test(str)</code> or <code>!!/sub$/.exec(str)</code>
|
|
is shorter than <code>Ox.ends(str, sub)</code>.
|
|
> 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 <f> Highlight matches in a string
|
|
> Ox.highlight('foobar', 'foo', 'match')
|
|
'<span class="match">foo</span>bar'
|
|
@*/
|
|
// 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'),
|
|
'<span class="' + classname + '">$1</span>'
|
|
) : txt;
|
|
};
|
|
|
|
/*@
|
|
Ox.highlightHTML <f> Highlight matches in an HTML string
|
|
> Ox.highlightHTML('<b>foo</b>bar', 'foobar', 'h')
|
|
'<b><span class="match">foo</span></b><span class="h">bar</span>'
|
|
> Ox.highlightHTML('<a href="/foo">foo</a>bar', 'foobar', 'h')
|
|
'<a href="/foo"><span class="h">foo</span></a><span class="h">bar</span>'
|
|
> Ox.highlightHTML('foo<br>bar', 'foobar', 'h')
|
|
'foo<br>bar'
|
|
> Ox.highlightHTML('AT&T', 'AT&T', 'h')
|
|
'<span class="h">AT&T</span>'
|
|
> Ox.highlightHTML('AT&T', 'amp', 'h')
|
|
'AT&T'
|
|
> Ox.highlightHTML('a <b> c', '<b>', 'h')
|
|
'<span class="h">a <span class="h"><b></span> c'
|
|
@*/
|
|
Ox.highlightHTML = function(html, str, classname, tags) {
|
|
var count = 0,
|
|
isEntity = false,
|
|
isTag = false,
|
|
position,
|
|
positions = [];
|
|
tags = Ox.merge(tags || [], ['a', 'b', 'code', 'i', 's', 'sub', 'sup', 'u']);
|
|
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 = '<span class="' + classname + '">'
|
|
+ html.substr(position[0], position[1] - position[0])
|
|
.replace(/(<.*?>)/g, '</span>$1<span class="' + classname + '">')
|
|
+ '</span>';
|
|
html = html.substr(0, position[0]) + match + html.substr(position[1]);
|
|
});
|
|
return html;
|
|
}
|
|
|
|
/*@
|
|
Ox.isValidEmail <f> Tests if a string is a valid e-mail address
|
|
(str) -> <b> True if the string is a valid e-mail address
|
|
str <s> 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 <f> 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 <f> Returns the components of a path
|
|
(str) -> <o> Path
|
|
extension <s> File extension
|
|
filename <s> Filename
|
|
pathname <s> 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 <f> Parses an srt subtitle file
|
|
(str) -> <o> Parsed subtitles
|
|
in <n> In point (sec)
|
|
out <n> Out point (sec)
|
|
text <s> Text
|
|
str <s> 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 <f> 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 <f> Reverses a string
|
|
> Ox.reverse('foobar')
|
|
'raboof'
|
|
@*/
|
|
Ox.reverse = function(str) {
|
|
return str.toString().split('').reverse().join('');
|
|
};
|
|
|
|
/*@
|
|
Ox.startsWith <f> Checks if a string starts with a given substring
|
|
If the substring is a string literal (and not a variable),
|
|
<code>/^sub/.test(str)</code> or <code>!!/^sub/.exec(str)</code>
|
|
is shorter than <code>Ox.starts(str, sub)</code>.
|
|
> 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 <f> Strips HTML tags from a string
|
|
> Ox.stripTags('f<span>o</span>o')
|
|
'foo'
|
|
@*/
|
|
Ox.stripTags = function(str) {
|
|
return str.replace(/<.*?>/g, '');
|
|
};
|
|
|
|
/*@
|
|
Ox.toCamelCase <f> 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 <f> 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 <f> 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 <f> 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 <f> 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 <f> Truncate a string to a given length
|
|
(string, length) <s> Truncated string
|
|
(string, length, position) -> <s> Truncated string
|
|
(string, length, placeholder) -> <s> Truncated string
|
|
(string, length, position, placeholder) -> <s> 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 <f> Splits a string into words, removing punctuation
|
|
(string) -> <[s]> Array of words
|
|
string <s> 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 <f> Wrap a string at word boundaries
|
|
> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 25, '<br/>')
|
|
"Anticonstitutionellement, <br/>Paris s'eveille"
|
|
> Ox.wordwrap("Anticonstitutionellement, Paris s'eveille", 16, '<br/>')
|
|
"Anticonstitution<br/>ellement, Paris <br/>s'eveille"
|
|
> Ox.wordwrap('These are short words', 16, '<br/>', true)
|
|
'These are <br/>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 || '<br/>',
|
|
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));
|
|
};
|