/**
*
* Pollyfils for often used functionality for Strings
*
* <i>Copyright (c) 2014 ITSA - https://github.com/itsa</i>
* New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
*
* @module js-ext
* @submodule lib/string.js
* @class String
*
*/
"use strict";
(function(StringPrototype) {
var SUBREGEX = /\{\s*([^|}]+?)\s*(?:\|([^}]*))?\s*\}/g,
DATEPATTERN = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/,
WHITESPACE_CLASS = "[\\s\uFEFF\xA0]+",
TRIM_LEFT_REGEX = new RegExp('^' + WHITESPACE_CLASS),
TRIM_RIGHT_REGEX = new RegExp(WHITESPACE_CLASS + '$'),
TRIMREGEX = new RegExp(TRIM_LEFT_REGEX.source + '|' + TRIM_RIGHT_REGEX.source, 'g'),
PATTERN_EMAIL = new RegExp('^[\\w!#$%&\'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&\'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\\.)+[a-zA-Z]{2,}$'),
PATTERN_URLEND = '([a-zA-Z0-9]+\\.)*(?:[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\\.)+[a-zA-Z]{2,}(/[\\w-]+)*$',
PATTERN_URLHTTP = new RegExp('^http://'+PATTERN_URLEND),
PATTERN_URLHTTPS = new RegExp('^https://'+PATTERN_URLEND),
PATTERN_URL = new RegExp('^(https?://)?'+PATTERN_URLEND),
PATTERN_INTEGER = /^(([-]?[1-9][0-9]*)|0)$/,
PATTERN_FLOAT_START = '^([-]?(([1-9][0-9]*)|0))?(\\',
PATTERN_FLOAT_END = '[0-9]+)?$',
PATTERN_FLOAT_COMMA = new RegExp(PATTERN_FLOAT_START + ',' + PATTERN_FLOAT_END),
PATTERN_FLOAT_DOT = new RegExp(PATTERN_FLOAT_START + '.' + PATTERN_FLOAT_END),
PATTERN_HEX_COLOR_ALPHA = /^#?[0-9A-F]{4}([0-9A-F]{4})?$/,
PATTERN_HEX_COLOR = /^#?[0-9A-F]{3}([0-9A-F]{3})?$/,
replaceBKP;
/**
* Checks whether the substring is part if this String.
* Alias for (String.indexOf(substring) > -1)
*
* @method contains
* @param substring {String} the substring to test for
* @param [caseInsensitive=false] {Boolean} whether to ignore case-sensivity
* @return {Boolean} whether the substring is found
*/
String.contains || (StringPrototype.contains=function(substring, caseInsensitive) {
return caseInsensitive ? (this.toLowerCase().indexOf(substring.toLowerCase()) > -1) : (this.indexOf(substring) > -1);
});
/**
* Checks if the string ends with the value specified by `test`
*
* @method endsWith
* @param test {String} the string to test for
* @param [caseInsensitive=false] {Boolean} whether to ignore case-sensivity
* @return {Boolean} whether the string ends with `test`
*/
// NOTE: we ALWAYS set this method --> ES6 native `startsWiths` lacks the second argument
StringPrototype.endsWith=function(test, caseInsensitive) {
return (new RegExp(test+'$', caseInsensitive ? 'i': '')).test(this);
};
/**
* Checks if the string can be parsed into a number when using `parseInt()`
*
* @method parsable
* @return {Boolean} whether the string is parsable
*/
String.parsable || (StringPrototype.parsable=function() {
// strange enough, NaN doen't let compare itself, so we need a strange test:
// parseInt(value, 10)===parseInt(value, 10)
// which returns `true` for a parsable value, otherwise false
return (parseInt(this)===parseInt(this));
});
/**
* Checks if the string starts with the value specified by `test`
*
* @method startsWith
* @param test {String} the string to test for
* @param [caseInsensitive=false] {Boolean} whether to ignore case-sensivity
* @return {Boolean} whether the string starts with `test`
*/
// NOTE: we ALWAYS set this method --> ES6 native `startsWiths` lacks the second argument
StringPrototype.startsWith=function(test, caseInsensitive) {
return (new RegExp('^'+test, caseInsensitive ? 'i': '')).test(this);
};
/**
* Performs `{placeholder}` substitution on a string. The object passed
* provides values to replace the `{placeholder}`s.
* `{placeholder}` token names must match property names of the object.
*
* `{placeholder}` tokens that are undefined on the object map will be removed.
*
* @example
* var greeting = '{message} {who}!';
* greeting.substitute({message: 'Hello'}); // results into 'Hello !'
*
* @method substitute
* @param obj {Object} Object containing replacement values.
* @return {String} the substitute result.
*/
String.substitute || (StringPrototype.substitute=function(obj) {
return this.replace(SUBREGEX, function (match, key) {
return (obj[key]===undefined) ? '' : obj[key];
});
});
/**
* Returns a ISO-8601 Date-object build by the String's value.
* If the String-value doesn't match ISO-8601, `null` will be returned.
*
* ISO-8601 Date's are generated by JSON.stringify(), so it's very handy to be able to reconvert them.
*
* @example
* var birthday = '2010-02-10T14:45:30.000Z';
* birthday.toDate(); // --> Wed Feb 10 2010 15:45:30 GMT+0100 (CET)
*
* @method toDate
* @return {Date|null} the Date represented by the String's value or null when invalid
*/
String.toDate || (StringPrototype.toDate=function() {
return DATEPATTERN.test(this) ? new Date(this) : null;
});
/**
* Generated the string without any white-spaces at the start or end.
*
* @method trim
* @return {String} new String without leading and trailing white-spaces
*/
String.trim || (StringPrototype.trim=function() {
return this.replace(TRIMREGEX, '');
});
/**
* Generated the string without any white-spaces at the beginning.
*
* @method trimLeft
* @return {String} new String without leading white-spaces
*/
String.trimLeft || (StringPrototype.trimLeft=function() {
return this.replace(TRIM_LEFT_REGEX, '');
});
/**
* Generated the string without any white-spaces at the end.
*
* @method trimRight
* @return {String} new String without trailing white-spaces
*/
String.trimRight || (StringPrototype.trimRight=function() {
return this.replace(TRIM_RIGHT_REGEX, '');
});
/**
* Replaces search-characters by a replacement. Replaces only the firts occurence.
* Does not alter the String itself, but returns a new String with the replacement.
*
* @method replace
* @param search {String} the character(s) to be replaced
* @param replacement {String} the replacement
* @param [caseInsensitive=false] {Boolean} whether to do search case-insensitive
* @return {String} new String with the replacement
*/
replaceBKP = StringPrototype.replace;
StringPrototype.replace=function(search, replacement, caseInsensitive) {
var re;
if (caseInsensitive) {
re = new RegExp(search, 'i');
return this.replace(re, replacement);
}
else {
return replaceBKP.apply(this, arguments);
}
};
/**
* Replaces search-characters by a replacement. Replaces all occurences.
* Does not alter the String itself, but returns a new String with the replacements.
*
* @method replaceAll
* @param search {String} the character(s) to be replaced
* @param replacement {String} the replacement
* @param [caseInsensitive=false] {Boolean} whether to do search case-insensitive
* @return {String} new String with the replacements
*/
String.replaceAll || (StringPrototype.replaceAll=function(search, replacement, caseInsensitive) {
var re = new RegExp(search, 'g' + (caseInsensitive ? 'i' : ''));
return this.replace(re, replacement);
});
/**
* Validates if the String's value represents a valid emailaddress.
*
* @method validateEmail
* @return {Boolean} whether the String's value is a valid emailaddress.
*/
StringPrototype.validateEmail = function() {
return PATTERN_EMAIL.test(this);
};
/**
* Validates if the String's value represents a valid floated number.
*
* @method validateFloat
* @param [comma] {Boolean} whether to use a comma as decimal separator instead of a dot
* @return {Boolean} whether the String's value is a valid floated number.
*/
StringPrototype.validateFloat = function(comma) {
return comma ? PATTERN_FLOAT_COMMA.test(this) : PATTERN_FLOAT_DOT.test(this);
};
/**
* Validates if the String's value represents a hexadecimal color.
*
* @method validateHexaColor
* @param [alpha=false] {Boolean} whether to accept alpha transparancy
* @return {Boolean} whether the String's value is a valid hexadecimal color.
*/
StringPrototype.validateHexaColor = function(alpha) {
return alpha ? PATTERN_HEX_COLOR_ALPHA.test(this) : PATTERN_HEX_COLOR.test(this);
};
/**
* Validates if the String's value represents a valid integer number.
*
* @method validateNumber
* @return {Boolean} whether the String's value is a valid integer number.
*/
StringPrototype.validateNumber = function() {
return PATTERN_INTEGER.test(this);
};
/**
* Validates if the String's value represents a valid boolean.
*
* @method validateBoolean
* @return {Boolean} whether the String's value is a valid boolean.
*/
StringPrototype.validateBoolean = function() {
var length = this.length,
check;
if ((length<4) || (length>5)) {
return false;
}
check = this.toUpperCase();
return ((check==='TRUE') || (check==='FALSE'));
};
/**
* Validates if the String's value represents a valid Date.
*
* @method validateDate
* @return {Boolean} whether the String's value is a valid Date object.
*/
StringPrototype.validateDate = function() {
return DATEPATTERN.test(this);
};
/**
* Validates if the String's value represents a valid URL.
*
* @method validateURL
* @param [options] {Object}
* @param [options.http] {Boolean} to force matching starting with `http://`
* @param [options.https] {Boolean} to force matching starting with `https://`
* @return {Boolean} whether the String's value is a valid URL.
*/
StringPrototype.validateURL = function(options) {
var instance = this;
options || (options={});
if (options.http && options.https) {
return false;
}
return options.http ? PATTERN_URLHTTP.test(instance) : (options.https ? PATTERN_URLHTTPS.test(instance) : PATTERN_URL.test(instance));
};
}(String.prototype));