/*!
* Module Strings
*/
/**
* @namespace Strings
*/
const MODULE_NAME = 'Strings';
//###[ IMPORTS ]########################################################################################################
import {isFunction, isArray, orDefault, isNaN, hasValue, isPlainObject, round} from './basic.js';
//###[ DATA ]###########################################################################################################
const SLUGIFY_LATINMAP = {
'Á':'A','Ă':'A','Ắ':'A','Ặ':'A','Ằ':'A','Ẳ':'A','Ẵ':'A','Ǎ':'A','Â':'A','Ấ':'A','Ậ':'A','Ầ':'A','Ẩ':'A','Ẫ':'A',
'Ä':'Ae','Ǟ':'A','Ȧ':'A','Ǡ':'A','Ạ':'A','Ȁ':'A','À':'A','Ả':'A','Ȃ':'A','Ā':'A','Ą':'A','Å':'A','Ǻ':'A','Ḁ':'A',
'Ⱥ':'A','Ã':'A','Ꜳ':'AA','Æ':'AE','Ǽ':'AE','Ǣ':'AE','Ꜵ':'AO','Ꜷ':'AU','Ꜹ':'AV','Ꜻ':'AV','Ꜽ':'AY','Ḃ':'B',
'Ḅ':'B','Ɓ':'B','Ḇ':'B','Ƀ':'B','Ƃ':'B','Ć':'C','Č':'C','Ç':'C','Ḉ':'C','Ĉ':'C','Ċ':'C','Ƈ':'C','Ȼ':'C','Ď':'D',
'Ḑ':'D','Ḓ':'D','Ḋ':'D','Ḍ':'D','Ɗ':'D','Ḏ':'D','Dz':'D','Dž':'D','Đ':'D','Ƌ':'D','DZ':'DZ','DŽ':'DZ','É':'E','Ĕ':'E',
'Ě':'E','Ȩ':'E','Ḝ':'E','Ê':'E','Ế':'E','Ệ':'E','Ề':'E','Ể':'E','Ễ':'E','Ḙ':'E','Ë':'E','Ė':'E','Ẹ':'E','Ȅ':'E',
'È':'E','Ẻ':'E','Ȇ':'E','Ē':'E','Ḗ':'E','Ḕ':'E','Ę':'E','Ɇ':'E','Ẽ':'E','Ḛ':'E','Ꝫ':'ET','Ḟ':'F','Ƒ':'F','Ǵ':'G',
'Ğ':'G','Ǧ':'G','Ģ':'G','Ĝ':'G','Ġ':'G','Ɠ':'G','Ḡ':'G','Ǥ':'G','Ḫ':'H','Ȟ':'H','Ḩ':'H','Ĥ':'H','Ⱨ':'H','Ḧ':'H',
'Ḣ':'H','Ḥ':'H','Ħ':'H','Í':'I','Ĭ':'I','Ǐ':'I','Î':'I','Ï':'I','Ḯ':'I','İ':'I','Ị':'I','Ȉ':'I','Ì':'I','Ỉ':'I',
'Ȋ':'I','Ī':'I','Į':'I','Ɨ':'I','Ĩ':'I','Ḭ':'I','Ꝺ':'D','Ꝼ':'F','Ᵹ':'G','Ꞃ':'R','Ꞅ':'S','Ꞇ':'T','Ꝭ':'IS','Ĵ':'J',
'Ɉ':'J','Ḱ':'K','Ǩ':'K','Ķ':'K','Ⱪ':'K','Ꝃ':'K','Ḳ':'K','Ƙ':'K','Ḵ':'K','Ꝁ':'K','Ꝅ':'K','Ĺ':'L','Ƚ':'L','Ľ':'L',
'Ļ':'L','Ḽ':'L','Ḷ':'L','Ḹ':'L','Ⱡ':'L','Ꝉ':'L','Ḻ':'L','Ŀ':'L','Ɫ':'L','Lj':'L','Ł':'L','LJ':'LJ','Ḿ':'M','Ṁ':'M',
'Ṃ':'M','Ɱ':'M','Ń':'N','Ň':'N','Ņ':'N','Ṋ':'N','Ṅ':'N','Ṇ':'N','Ǹ':'N','Ɲ':'N','Ṉ':'N','Ƞ':'N','Nj':'N','Ñ':'N',
'NJ':'NJ','Ó':'O','Ŏ':'O','Ǒ':'O','Ô':'O','Ố':'O','Ộ':'O','Ồ':'O','Ổ':'O','Ỗ':'O','Ö':'Oe','Ȫ':'O','Ȯ':'O','Ȱ':'O',
'Ọ':'O','Ő':'O','Ȍ':'O','Ò':'O','Ỏ':'O','Ơ':'O','Ớ':'O','Ợ':'O','Ờ':'O','Ở':'O','Ỡ':'O','Ȏ':'O','Ꝋ':'O','Ꝍ':'O',
'Ō':'O','Ṓ':'O','Ṑ':'O','Ɵ':'O','Ǫ':'O','Ǭ':'O','Ø':'O','Ǿ':'O','Õ':'O','Ṍ':'O','Ṏ':'O','Ȭ':'O','Ƣ':'OI','Ꝏ':'OO',
'Ɛ':'E','Ɔ':'O','Ȣ':'OU','Ṕ':'P','Ṗ':'P','Ꝓ':'P','Ƥ':'P','Ꝕ':'P','Ᵽ':'P','Ꝑ':'P','Ꝙ':'Q','Ꝗ':'Q','Ŕ':'R','Ř':'R',
'Ŗ':'R','Ṙ':'R','Ṛ':'R','Ṝ':'R','Ȑ':'R','Ȓ':'R','Ṟ':'R','Ɍ':'R','Ɽ':'R','Ꜿ':'C','Ǝ':'E','Ś':'S','Ṥ':'S','Š':'S',
'Ṧ':'S','Ş':'S','Ŝ':'S','Ș':'S','Ṡ':'S','Ṣ':'S','Ṩ':'S','ẞ':'Ss','Ť':'T','Ţ':'T','Ṱ':'T','Ț':'T','Ⱦ':'T','Ṫ':'T',
'Ṭ':'T','Ƭ':'T','Ṯ':'T','Ʈ':'T','Ŧ':'T','Ɐ':'A','Ꞁ':'L','Ɯ':'M','Ʌ':'V','Ꜩ':'TZ','Ú':'U','Ŭ':'U','Ǔ':'U','Û':'U',
'Ṷ':'U','Ü':'Ue','Ǘ':'U','Ǚ':'U','Ǜ':'U','Ǖ':'U','Ṳ':'U','Ụ':'U','Ű':'U','Ȕ':'U','Ù':'U','Ủ':'U','Ư':'U','Ứ':'U',
'Ự':'U','Ừ':'U','Ử':'U','Ữ':'U','Ȗ':'U','Ū':'U','Ṻ':'U','Ų':'U','Ů':'U','Ũ':'U','Ṹ':'U','Ṵ':'U','Ꝟ':'V','Ṿ':'V',
'Ʋ':'V','Ṽ':'V','Ꝡ':'VY','Ẃ':'W','Ŵ':'W','Ẅ':'W','Ẇ':'W','Ẉ':'W','Ẁ':'W','Ⱳ':'W','Ẍ':'X','Ẋ':'X','Ý':'Y','Ŷ':'Y',
'Ÿ':'Y','Ẏ':'Y','Ỵ':'Y','Ỳ':'Y','Ƴ':'Y','Ỷ':'Y','Ỿ':'Y','Ȳ':'Y','Ɏ':'Y','Ỹ':'Y','Ź':'Z','Ž':'Z','Ẑ':'Z','Ⱬ':'Z',
'Ż':'Z','Ẓ':'Z','Ȥ':'Z','Ẕ':'Z','Ƶ':'Z','IJ':'IJ','Œ':'OE','ᴀ':'A','ᴁ':'AE','ʙ':'B','ᴃ':'B','ᴄ':'C','ᴅ':'D','ᴇ':'E',
'ꜰ':'F','ɢ':'G','ʛ':'G','ʜ':'H','ɪ':'I','ʁ':'R','ᴊ':'J','ᴋ':'K','ʟ':'L','ᴌ':'L','ᴍ':'M','ɴ':'N','ᴏ':'O','ɶ':'OE',
'ᴐ':'O','ᴕ':'OU','ᴘ':'P','ʀ':'R','ᴎ':'N','ᴙ':'R','ꜱ':'S','ᴛ':'T','ⱻ':'E','ᴚ':'R','ᴜ':'U','ᴠ':'V','ᴡ':'W','ʏ':'Y',
'ᴢ':'Z','á':'a','ă':'a','ắ':'a','ặ':'a','ằ':'a','ẳ':'a','ẵ':'a','ǎ':'a','â':'a','ấ':'a','ậ':'a','ầ':'a','ẩ':'a',
'ẫ':'a','ä':'ae','ǟ':'a','ȧ':'a','ǡ':'a','ạ':'a','ȁ':'a','à':'a','ả':'a','ȃ':'a','ā':'a','ą':'a','ᶏ':'a','ẚ':'a',
'å':'a','ǻ':'a','ḁ':'a','ⱥ':'a','ã':'a','ꜳ':'aa','æ':'ae','ǽ':'ae','ǣ':'ae','ꜵ':'ao','ꜷ':'au','ꜹ':'av','ꜻ':'av',
'ꜽ':'ay','ḃ':'b','ḅ':'b','ɓ':'b','ḇ':'b','ᵬ':'b','ᶀ':'b','ƀ':'b','ƃ':'b','ɵ':'o','ć':'c','č':'c','ç':'c','ḉ':'c',
'ĉ':'c','ɕ':'c','ċ':'c','ƈ':'c','ȼ':'c','ď':'d','ḑ':'d','ḓ':'d','ȡ':'d','ḋ':'d','ḍ':'d','ɗ':'d','ᶑ':'d','ḏ':'d',
'ᵭ':'d','ᶁ':'d','đ':'d','ɖ':'d','ƌ':'d','ı':'i','ȷ':'j','ɟ':'j','ʄ':'j','dz':'dz','dž':'dz','é':'e','ĕ':'e','ě':'e',
'ȩ':'e','ḝ':'e','ê':'e','ế':'e','ệ':'e','ề':'e','ể':'e','ễ':'e','ḙ':'e','ë':'e','ė':'e','ẹ':'e','ȅ':'e','è':'e',
'ẻ':'e','ȇ':'e','ē':'e','ḗ':'e','ḕ':'e','ⱸ':'e','ę':'e','ᶒ':'e','ɇ':'e','ẽ':'e','ḛ':'e','ꝫ':'et','ḟ':'f','ƒ':'f',
'ᵮ':'f','ᶂ':'f','ǵ':'g','ğ':'g','ǧ':'g','ģ':'g','ĝ':'g','ġ':'g','ɠ':'g','ḡ':'g','ᶃ':'g','ǥ':'g','ḫ':'h','ȟ':'h',
'ḩ':'h','ĥ':'h','ⱨ':'h','ḧ':'h','ḣ':'h','ḥ':'h','ɦ':'h','ẖ':'h','ħ':'h','ƕ':'hv','í':'i','ĭ':'i','ǐ':'i','î':'i',
'ï':'i','ḯ':'i','ị':'i','ȉ':'i','ì':'i','ỉ':'i','ȋ':'i','ī':'i','į':'i','ᶖ':'i','ɨ':'i','ĩ':'i','ḭ':'i','ꝺ':'d',
'ꝼ':'f','ᵹ':'g','ꞃ':'r','ꞅ':'s','ꞇ':'t','ꝭ':'is','ǰ':'j','ĵ':'j','ʝ':'j','ɉ':'j','ḱ':'k','ǩ':'k','ķ':'k','ⱪ':'k',
'ꝃ':'k','ḳ':'k','ƙ':'k','ḵ':'k','ᶄ':'k','ꝁ':'k','ꝅ':'k','ĺ':'l','ƚ':'l','ɬ':'l','ľ':'l','ļ':'l','ḽ':'l','ȴ':'l',
'ḷ':'l','ḹ':'l','ⱡ':'l','ꝉ':'l','ḻ':'l','ŀ':'l','ɫ':'l','ᶅ':'l','ɭ':'l','ł':'l','lj':'lj','ſ':'s','ẜ':'s','ẛ':'s',
'ẝ':'s','ḿ':'m','ṁ':'m','ṃ':'m','ɱ':'m','ᵯ':'m','ᶆ':'m','ń':'n','ň':'n','ņ':'n','ṋ':'n','ȵ':'n','ṅ':'n','ṇ':'n',
'ǹ':'n','ɲ':'n','ṉ':'n','ƞ':'n','ᵰ':'n','ᶇ':'n','ɳ':'n','ñ':'n','nj':'nj','ó':'o','ŏ':'o','ǒ':'o','ô':'o','ố':'o',
'ộ':'o','ồ':'o','ổ':'o','ỗ':'o','ö':'oe','ȫ':'o','ȯ':'o','ȱ':'o','ọ':'o','ő':'o','ȍ':'o','ò':'o','ỏ':'o','ơ':'o',
'ớ':'o','ợ':'o','ờ':'o','ở':'o','ỡ':'o','ȏ':'o','ꝋ':'o','ꝍ':'o','ⱺ':'o','ō':'o','ṓ':'o','ṑ':'o','ǫ':'o','ǭ':'o',
'ø':'o','ǿ':'o','õ':'o','ṍ':'o','ṏ':'o','ȭ':'o','ƣ':'oi','ꝏ':'oo','ɛ':'e','ᶓ':'e','ɔ':'o','ᶗ':'o','ȣ':'ou','ṕ':'p',
'ṗ':'p','ꝓ':'p','ƥ':'p','ᵱ':'p','ᶈ':'p','ꝕ':'p','ᵽ':'p','ꝑ':'p','ꝙ':'q','ʠ':'q','ɋ':'q','ꝗ':'q','ŕ':'r','ř':'r',
'ŗ':'r','ṙ':'r','ṛ':'r','ṝ':'r','ȑ':'r','ɾ':'r','ᵳ':'r','ȓ':'r','ṟ':'r','ɼ':'r','ᵲ':'r','ᶉ':'r','ɍ':'r','ɽ':'r',
'ↄ':'c','ꜿ':'c','ɘ':'e','ɿ':'r','ś':'s','ṥ':'s','š':'s','ṧ':'s','ş':'s','ŝ':'s','ș':'s','ṡ':'s','ṣ':'s','ṩ':'s',
'ʂ':'s','ᵴ':'s','ᶊ':'s','ȿ':'s','ɡ':'g','ß':'ss','ᴑ':'o','ᴓ':'o','ᴝ':'u','ť':'t','ţ':'t','ṱ':'t','ț':'t','ȶ':'t',
'ẗ':'t','ⱦ':'t','ṫ':'t','ṭ':'t','ƭ':'t','ṯ':'t','ᵵ':'t','ƫ':'t','ʈ':'t','ŧ':'t','ᵺ':'th','ɐ':'a','ᴂ':'ae','ǝ':'e',
'ᵷ':'g','ɥ':'h','ʮ':'h','ʯ':'h','ᴉ':'i','ʞ':'k','ꞁ':'l','ɯ':'m','ɰ':'m','ᴔ':'oe','ɹ':'r','ɻ':'r','ɺ':'r','ⱹ':'r',
'ʇ':'t','ʌ':'v','ʍ':'w','ʎ':'y','ꜩ':'tz','ú':'u','ŭ':'u','ǔ':'u','û':'u','ṷ':'u','ü':'ue','ǘ':'u','ǚ':'u','ǜ':'u',
'ǖ':'u','ṳ':'u','ụ':'u','ű':'u','ȕ':'u','ù':'u','ủ':'u','ư':'u','ứ':'u','ự':'u','ừ':'u','ử':'u','ữ':'u','ȗ':'u',
'ū':'u','ṻ':'u','ų':'u','ᶙ':'u','ů':'u','ũ':'u','ṹ':'u','ṵ':'u','ᵫ':'ue','ꝸ':'um','ⱴ':'v','ꝟ':'v','ṿ':'v','ʋ':'v',
'ᶌ':'v','ⱱ':'v','ṽ':'v','ꝡ':'vy','ẃ':'w','ŵ':'w','ẅ':'w','ẇ':'w','ẉ':'w','ẁ':'w','ⱳ':'w','ẘ':'w','ẍ':'x','ẋ':'x',
'ᶍ':'x','ý':'y','ŷ':'y','ÿ':'y','ẏ':'y','ỵ':'y','ỳ':'y','ƴ':'y','ỷ':'y','ỿ':'y','ȳ':'y','ẙ':'y','ɏ':'y','ỹ':'y',
'ź':'z','ž':'z','ẑ':'z','ʑ':'z','ⱬ':'z','ż':'z','ẓ':'z','ȥ':'z','ẕ':'z','ᵶ':'z','ᶎ':'z','ʐ':'z','ƶ':'z','ɀ':'z',
'ff':'ff','ffi':'ffi','ffl':'ffl','fi':'fi','fl':'fl','ij':'ij','œ':'oe','st':'st','ₐ':'a','ₑ':'e','ᵢ':'i','ⱼ':'j','ₒ':'o',
'ᵣ':'r','ᵤ':'u','ᵥ':'v','ₓ':'x'
};
//###[ EXPORTS ]########################################################################################################
/**
* @namespace Strings:replace
*/
/**
* Offers similar functionality to PHP's str_replace or ES2021's replaceAll and avoids RegExps for this task.
* Replaces occurrences of search in subject with replace. search and replace may be arrays.
* If search is an array and replace is a string, all phrases in the array will be replaced with one string.
* If replace is an array itself, phrases and replacements are matched by index.
* Missing replacements are treated as an empty string (for example: if your array lengths do not match).
*
* Uses String.prototype.replaceAll internally, if available.
*
* @param {String} subject - the string to replace in
* @param {(String|String[])} search - the string(s) to replace
* @param {String|String[]} replace - the string(s) to replace the search string(s)
* @returns {String} the modified string
*
* @memberof Strings:replace
* @alias replace
* @example
* const sanitizedString = replace([':', '#', '-'], '_', exampleString);
*/
export function replace(subject, search, replace){
subject = `${subject}`;
search = [].concat(search);
replace = [].concat(replace);
let tmp = '';
search.forEach((searchTerm, index) => {
tmp = (replace.length > 1) ? ((replace[index] !== undefined) ? replace[index] : '') : replace[0];
if( isFunction(String.prototype.replaceAll) ){
subject = subject.replaceAll(`${searchTerm}`, `${tmp}`);
} else {
subject = subject.split(`${searchTerm}`).join(`${tmp}`);
}
});
return subject;
}
/**
* @namespace Strings:truncate
*/
/**
* Truncates a given string after a certain number of characters to enforce length restrictions.
*
* @param {String} subject - the string to check and truncate
* @param {?Number} [maxLength=30] - the maximum allowed character length for the string
* @param {?String} [suffix='...'] - the trailing string to end a truncated string with
* @throws error if suffix length is bigger than defined maxLength
* @returns {String} the (truncated) subject
*
* @memberof Strings:truncate
* @alias truncate
* @example
* const truncatedString = truncate(string, 10, '...');
*/
export function truncate(subject, maxLength=30, suffix='...'){
subject = `${subject}`;
maxLength = orDefault(maxLength, 30, 'int');
suffix = orDefault(suffix, '...', 'str');
if( suffix.length > maxLength ){
throw new Error(`${MODULE_NAME}:truncate | suffix cannot be longer than maxLength`);
}
if( subject.length > maxLength ){
subject = `${subject.slice(0, maxLength - suffix.length)}${suffix}`;
}
return subject;
}
/**
* @namespace Strings:pad
*/
/**
* Adds characters to strings, which are not yet of a defined expected length.
*
* A usual use-case for this would be, to add zeroes to a number, to fit a format like ISO dates.
*
* @param {String} subject - the string to pad
* @param {String} paddingCharacter - the character to use for padding, only first character is used
* @param {Number} expectedLength - the number of characters, the result has to have at least
* @param {String} [from='left'] - the number of characters, the result has to have at least
* @returns {String} the padded value, will always be cast to a string
*
* @memberof Strings:pad
* @alias pad
* @example
* pad(1, '0', 4, 'right')
* => '1000'
*/
export function pad(subject, paddingCharacter, expectedLength, from='left'){
subject = `${subject}`;
paddingCharacter = `${paddingCharacter}`[0];
expectedLength = orDefault(expectedLength, 2, 'int');
from = ['left', 'right'].includes(`${from}`) ? `${from}` : 'left';
const difference = expectedLength - subject.length;
if( difference > 0 ){
for( let i = 0; i < difference; i++ ){
subject = (from === 'right') ? `${subject}${paddingCharacter}` : `${paddingCharacter}${subject}`;
}
}
return subject;
}
/**
* @namespace Strings:trim
*/
/**
* Removes whitespace or characters from the beginning and the end (or just one side) of a string.
*
* This is usually used to sanitize values and remove leading or trailing whitespace in user input.
*
* @param {String} subject - the string to trim
* @param {?String|Array<String>} [characters=null] - defines which character(s) to trim; will trim whitespace if nullish (or '\\s'); if you want to trim whitespace and something else, define an array including a nullish value (or '\\s') as well as the other characters; this parameter uses regex definitions
* @param {?String} [from='both'] - the side(s) from which to trim, either "both", "left" or "right"
* @returns {String} the trimmed subject
*
* @memberof Strings:trim
* @alias trim
* @example
* trim(' foo ')
* => 'foo'
* trim(' foo ', null, 'right')
* => ' foo'
* trim('abcdefghijklmnopqrstuvwxyz', ['[a-f]', '[u-z]', 'l'], 'both')
* => 'ghijklmnopqrst'
*/
export function trim(subject, characters=null, from='both'){
subject = `${subject}`;
characters = [].concat(characters).map(char => !!char ? `${char}` : '\\s');
from = ['both', 'left', 'right'].includes(`${from}`) ? `${from}` : 'both';
if( ['both', 'left'].includes(from) ){
subject = subject.replace(new RegExp(`^(${characters.join('|')})+`, 'g'), '');
}
if( ['both', 'right'].includes(from) ){
subject = subject.replace(new RegExp(`(${characters.join('|')})+$`, 'g'), '');
}
return subject;
}
/**
* @namespace Strings:concat
*/
/**
* Simply concatenates strings with a glue part using array.join in a handy notation.
* You can also provide arguments to glue as a prepared array as the second parameter,
* in that case other parameters will be ignored.
*
* @param {?String} [glue=''] - the separator to use between single strings
* @param {?String[]} strings - list of strings to concatenate, either comma-separated or as single array
* @returns {String} the concatenated string
*
* @memberof Strings:concat
* @alias concat
* @example
* const finalCountdown = concat(' ... ', 10, 9, 8, 7, 6, '5', '4', '3', '2', '1', 'ZERO!');
* const finalCountdown = concat(' ... ', [10, 9, 8, 7, 6, '5', '4', '3', '2', '1', 'ZERO!']);
*/
export function concat(glue='', ...strings){
glue = orDefault(glue, '', 'str');
if( (strings.length > 0) && isArray(strings[0]) ){
return strings[0].join(glue);
} else {
return strings.join(glue);
}
}
/**
* @namespace Strings:format
*/
/**
* This is a pythonesque string format implementation.
* Apply formatted values to a string template, in which replacements are marked with curly braces.
*
* Display literal curly brace with {{ and }}.
*
* Unknown keys/indexes will be ignored.
*
* This solution is adapted from:
* https://github.com/davidchambers/string-format
*
* @param {String} template -
* @param {(String[]|Object)} replacements - arguments to insert into template, either as a dictionary, an array or a parameter sequence
* @throws general exception on syntax errors
* @returns {String} the formatted string
*
* @memberof Strings:format
* @alias format
* @example
* format('An elephant is {times:float(0.00)} times smarter than a {animal}', {times : 5.5555, animal : 'lion'})
* => 'An elephant is 5.56 times smarter than a lion'
* format('{0}{0}{0} ... {{BATMAN!}}', 'Nana')
* => 'NanaNanaNana ... {BATMAN!}'
* format('{} {} {} starts the alphabet.', 'A', 'B', 'C')
* => 'A B C starts the alphabet.'
* format('{0:int}, {1:int}, {2:int}: details are for wankers', '1a', 2.222, 3)
* => '1, 2, 3: details are for wankers'
*/
export function format(template, ...replacements){
template = `${template}`;
let
idx = 0,
explicit = false,
implicit = false
;
const fResolve = function(object, key){
const value = object[key];
if( isFunction(value) ){
return value.call(object);
} else {
return value;
}
};
const fLookup = function(object, key){
if( !/^(\d+)([.]|$)/.test(key) ){
key = `0.${key}`;
}
let match = /(.+?)[.](.+)/.exec(key);
while( match ){
object = fResolve(object, match[1]);
key = match[2];
match = /(.+?)[.](.+)/.exec(key);
}
return fResolve(object, key);
};
const formatters = {
int(value, radix){
radix = orDefault(radix, 10, 'int');
const res = parseInt(value, radix);
return !isNaN(res) ? `${res}` : '';
},
float(value, format){
format = orDefault(format, null, 'str');
let res = null;
if( hasValue(format) ){
let precision = 0;
try {
precision = format.split('.')[1].length;
} catch(ex) {
throw new Error(`${MODULE_NAME}:format | float precision arg malformed`);
}
res = round(value, precision)
} else {
res = parseFloat(value);
}
return !isNaN(res) ? `${res}` : '';
}
};
return template.replace(/([{}])\1|[{](.*?)(?:!(.+?))?[}]/g, function(match, literal, key){
let
ref = null,
value = '',
formatter = function(value){ return value; },
formatterArg = null
;
if( literal ){
return literal;
}
if( key.length ){
const keyParts = key.split(':');
if( keyParts.length > 1 ){
key = keyParts[0];
const
formatterParts = keyParts[1].split('('),
formatterName = formatterParts[0]
;
if( formatterParts.length > 1 ){
formatterArg = formatterParts[1].replace(')', '');
}
try {
formatter = formatters[formatterName];
} catch(ex) {
throw new Error(`${MODULE_NAME}:format | unknown formatter`);
}
}
if( implicit ){
throw new Error(`${MODULE_NAME}:format | cannot switch from implicit to explicit numbering`);
} else {
explicit = true;
}
ref = fLookup(replacements, key);
value = orDefault(ref, '');
} else {
if( explicit ){
throw new Error (`${MODULE_NAME}:format | cannot switch from explicit to implicit numbering`);
} else {
implicit = true;
}
ref = replacements[idx];
value = orDefault(ref, '');
idx++;
}
return formatter(value, formatterArg);
});
}
/**
* @namespace Strings:slugify
*/
/**
* Slugifies a text for use in a URL or id/class/attribute.
* Transforms accented characters to non-accented ones.
* Throws out everything except basic A-Z characters and numbers after replacements have taken place.
* Provide own replacements, supplementing or overriding the default replacement map to cover special cases
* (will take precedence over the default map).
*
* @param {String} text - the text to slugify
* @param {?String} [additionalMap=null] - optional character map to supplement/override the default map, having the form {'[search character]' : '[replacement]', ...}
* @param {?Boolean} [toLowerCase=true] - set this to false, to keep character casing
* @returns {String} the slugified string
*
* @memberof Strings:slugify
* @alias slugify
* @example
* slugify('This is a cömplicated ßtring for URLs!')
* => 'this-is-a-coemplicated-sstring-for-urls'
*/
export function slugify(text, additionalMap=null, toLowerCase=true){
if( !isPlainObject(additionalMap) ){
additionalMap = {};
}
toLowerCase = orDefault(toLowerCase, true, 'bool');
text = `${text}`;
if( toLowerCase ){
text = text.toLowerCase();
}
return text
.replace(
/[^\-]/g, // replace accented chars with plain ones via map and/or apply additionalMap
char => additionalMap[char] ?? SLUGIFY_LATINMAP[char] ?? char
)
.replace(/\s+|_+/g, '-') // replace spaces and underscores with "-"
.replace(/[^\w\-]+/g, '') // remove all non-word, non-dash chars
.replace(/--+/g, '-') // replace multiple "-" with single "-"
.replace(/^-+/, '') // trim "-" from start of text
.replace(/-+$/, '') // trim "-" from end of text
;
}
/**
* @namespace Strings:maskForSelector
*/
/**
* Masks all selector-special-characters, to allow selecting elements with special characters in selector using
* querySelector and querySelectorAll (also works for jQuery and Cash).
*
* @param {String} str - the string to mask for use in a selector
* @returns {String} the masked string
*
* @memberof Strings:maskForSelector
* @alias maskForSelector
* @example
* document.querySelector(`#element_${maskForSelector(elementName)}`).classList.remove('test');
*/
export function maskForSelector(str){
return `${str}`.replace(/([#;&,.+*~':"!^$\[\]()=>|\/@])/g, '\\$&');
}
/**
* @namespace Strings:maskForRegEx
*/
/**
* Masks all regex special characters, to test or match a string using a regex, that contains
* characters used in regexes themselves.
*
* @param {String} str - the string to mask for use in a regexp
* @returns {String} the masked string
*
* @memberof Strings:maskForRegEx
* @alias maskForRegEx
* @example
* if( (new RegExp(`^${maskForRegEx(arbitraryString)}$')).test('abc') ){
* alert('are identical!');
* }
*/
export function maskForRegEx(str){
return `${str}`.replace(/([\-\[\]\/{}()*+?.\\^$|])/g, "\\$&");
}
/**
* @namespace Strings:maskForHtml
*/
/**
* Masks a string possibly containing reserved HTML chars for HTML output as is
* (so a < actually reads on the page).
*
* Only replaces critical chars like <>& with entities, but
* keeps non-critical unicode chars like »/.
*
* @param {String} text - the string to mask for use in HTML
* @returns {String} the masked string
*
* @memberof Strings:maskForHtml
* @alias maskForHtml
* @see unmaskFromHtml
* @example
* maskForHtml('</>&;üäöÜÄÖß– »')
* => '</>&;üäöÜÄÖß– »'
*/
export function maskForHtml(text){
const escape = document.createElement('textarea');
escape.textContent = `${text}`;
return escape.innerHTML;
}
/**
* @namespace Strings:unmaskFromHtml
*/
/**
* Replaces entities in a html-masked string with the vanilla characters
* thereby returning a real HTML string, which could, for example, be used
* to construct new elements with tag markup.
*
* @param {String} html - the string to unmask entities in
* @returns {String} the unmasked string
*
* @memberof Strings:unmaskFromHtml
* @alias unmaskFromHtml
* @see maskForHtml
* @example
* unmaskFromHtml('</>&;üäöÜÄÖß– »')
* => '</>&;üäöÜÄÖß– »'
*/
export function unmaskFromHtml(html){
const escape = document.createElement('textarea');
escape.innerHTML = `${html}`;
return escape.textContent;
}