@soei/format
Version:
字符串格式化: format(`query:?-?-?-?-{[0]>+1,1,2}`, +new Date, 1 > 2) => query:1711361589087-false-false-false-2
310 lines (299 loc) • 9.53 kB
JavaScript
let {
isFunction,
isArray,
iList2Array,
isNil,
isString,
isSimplyType,
merge,
iSplit,
each,
runer: iRuner
} = require('@soei/tools');
var Nil,
COMMA = ',',
SPACE = '',
r_A_Z = /(\d+)\-(\d+)/;
var rFormatSp = /(?:,)/;
/* @see iReplace(...) {\d-\d}匹配提前 $1 $2, 和{prop}部分匹配冲突, 当prop内含有 - 号时*/
var rFormat = /(?:{(\d+)\-(\d+)}|\{([^,{}]+)\}|\[([\w-]+)\]|\[([^\[\]\{\}'"]+)\]|([\w$\u4e00-\u9fa5]+)(\s*(?:=|:\s*))\?)|{([^,\}\{]+),([^,}]*),([^,}]*)}/g;
var rProperty = /(?=\s|^)(?:[+-]{2}|!+|)([\d_$a-z]+[_$\w]*)(?:(?:\??\.[_$\w]+|)(?:(?:\?\.|)\[(['"]*)[\d\w]+\2\]|))*(?:[+-]{2}|)/g,
filters = [
[/(?:\[(['"]*)([\d]+)\1\]|(?:this\.|^)(\d+))((?:\.[_$\w]+)*)/g, "this[$2$3]$4"]
];
// 内置函数映射
var BUILT_IN_FUNCTIONS = {
now: function () { return +new Date; }
}
// 缓存正则表达式
var REGEX_CACHE = {}
/**
* 获取或检查数据,并缓存正则表达式
* @param {string} chr - 输入字符串
* @param {string} prop - 属性名
* @returns {string} - 处理后的字符串
*/
function getOrCheckData(chr, prop) {
return isNaN(+prop) ? chr.replace(
REGEX_CACHE[prop] || (REGEX_CACHE[prop] = new RegExp("(" + prop + ")"))
,
'_$1'
) : chr;
}
/**
* 字符串批量替换
* @param {string} value - 原始字符串
* @param {Array} replacements - 替换规则列表
* @returns {string} - 替换后的字符串
*/
function batchReplace(value, replacements) {
for (var i = 0, val; val = replacements[i++];) {
value = SPACE.replace.apply(value, val);
}
return value;
}
// 获取表达式对应的值
function _GetExp2funcValue(expression, data, invalid) {
var ex, afunArgs;
expression = batchReplace(expression, filters);
if (afunArgs = expression.match(rProperty)) {
var values = [], args = [];
each(afunArgs, function (_, k, values, data, args) {
rProperty.lastIndex = 0;
k = rProperty.exec(k)[1];
_ = data[k];
if (!isNil(_)) {
args.push(getOrCheckData(k, k));
expression = getOrCheckData(expression, k);
values.push(isFunction(_) ? iRuner(_) : _);
}
}, values, data, args);
if (values.length || args.length || expression.indexOf('this') >= 0) {
ex = new Function(
// 参数
args.join(COMMA),
// 返回值
'return ' + expression
)/* 调用 */.apply(data, values);
} else {
return invalid;
}
} else {
ex = data[expression] || expression;
}
return ex;
}
/**
* 生成指定范围内的随机数或执行回调
* @param {number|string} start - 起始值
* @param {number} end - 结束值
* @param {Function} callback - 回调函数(可选)
* @param {boolean} brimless - 是否排除边界值
* @returns {number|void} - 随机数或回调结果
*/
function between(a, z, callback, brimless/* 无边缘 */) {
if (isString(a)) {
let az = a.match(r_A_Z);
if (az) {
z = az[2];
a = az[1];
}
}
var min, max, edge = +!!brimless;
if (+a < +z) {
min = +a;
max = +z;
} else {
min = +z;
max = +a;
}
var lof = (max - min) + 1 - 2 * edge;
if (isFunction(callback)) {
while (lof-- > 0) {
if (iRuner(callback, null, min + lof + edge, max, min)) break;
}
} else {
return (Math.random() * 1e6 >> 0) % lof + min;
}
}
var REGEX_PLACEHOLDER = /\?/;
var REGEX_PLACEHOLDER_G = /\?/g;
/**
* 替换字符串中的占位符 `?`
* @param {string} template - 模板字符串
* @param {Array|string} args - 参数列表
* @returns {string} - 替换后的字符串
*/
function replacePlaceholders(template, args) {
if (REGEX_PLACEHOLDER.test(template)) {
isArray(args) || (args = iList2Array(arguments), args.shift());
isString(args) && (args = iSplit(args));
var list = template.match(REGEX_PLACEHOLDER_G), pre;
for (var i = 0, lenth = list.length; i < lenth; i++) {
template = template.replace(REGEX_PLACEHOLDER, (pre = ((isNil(args[i]) ? pre : args[i]))));
}
}
return template;
}
/**
* 字符串格式化
* 'key=?|[val]|{1-19}|[1,2,3]'.format({key:1,val:2})
* key=1|2|3(1-19随机)|2(1,2,3)随机一个
* @Time 2018-11-13
*/
function iStringFormat(args) {
var host = this, k2 = arguments.length,
/* 判断宿主是否依附在 String.prototype 下 */
notInString = !isString(host) && isString(args);
if (
/* 2022-11-07 判断参数是否为format(String, {key: value} | [...] | 1, 2, 3, 4, 'a') */
k2 > 1
|| isSimplyType(args)
) {
args = iList2Array(arguments);
if (notInString) {
// 获取处理对象字符串
host = args.shift();
if (args.length == 1 && !isSimplyType(args[0]) /* 非基本类型 */) args = args.shift();
}
/* 功能分流 处理 caller('?............? ?', 1, 2, 3) */
if (REGEX_PLACEHOLDER.test(host) && isArray(args)) host = replacePlaceholders(host, args);
}
args || (args = {});
var has = !!args.now, ret;
merge(args, BUILT_IN_FUNCTIONS);
// 替换字符串内参数,区间等
try {
ret = (host + SPACE).replace(rFormat, iReplace.bind(args, args._groups__ = {}));
delete args._groups__;
has || (delete args.now);
} catch (e) {
ret = host;
}
return ret;
}
merge(
String.prototype,
{
format: iStringFormat,
on: iStringFormat
}
)
/**
* 替换函数核心逻辑
* @param {string} source - 匹配到的源字符串
* @param {string} a - 起始值
* @param {string} z - 结束值
* @param {string} arg - 属性名
* @param {string} prop - 表达式
* @param {string} list - 数组形式
* @param {string} attr - 属性名称
* @param {string} eq - 等号
* @param {string} condition - 条件表达式
* @param {string} trueVal - 真值
* @param {string} falseVal - 假值
* @returns {string} - 替换结果
*/
function iReplace(groups, source /*正则匹配的字符串*/, a /*开始取值范围*/, z /*结束*/,
prop/* {arg1}, {expr...} */, arg /*属性 [arg]*/,
list /*数组 [a, b, c]*/,
attr /*属性名称 attr=? 替换?为attr对应的属性值 配合后面 eq*/, eq /*等号*/,
condition, trueVal, falseVal
) {
var args = this;
if (groups && source in groups) return groups[source];
// 判断是否为数值
if (/^\d+$/.test(a + z)) {
return between(a, z);
}
/* 处理类似三目运算 exp(?)true(:)fasle `{true|false|props, value1, value2}` */
if (condition) {
return _GetExp2funcValue(
_GetExp2funcValue(condition, args)
? trueVal
: falseVal,
args
);
}
var val;
/*是否为数组 处理 `[first, ...]`*/
if (list) {
list = iSplit(list, rFormatSp);
/* [first, 2, 3, ...] first instanceof Function ? first(2, 3, 4,...) */
val = isFunction(args[list[0]])
?
args[list.shift()].apply(args, list)
:
list[between(0, list.length - 1)];
return val;
}
var key = prop || arg || attr;
if (args && key) {
try {
var invalid = Math.random();
val = _GetExp2funcValue(key, args, invalid);
return invalid == val ? source : eq ? (attr || SPACE) + eq + val : val;
} catch (e) {
MarkErr(e, source);
}
}
groups[source] = source;
return source;
}
function MarkErr(e, source) {
source = "@soei/format\n\n ╰─ " + source;
var stack = e.message || e.stack;
var errposition = stack.replace(/(?:.*(?:token|number|reading)(?:\:?\s+(["'])((?:(?!\1).)+)\1|).*|(\w+) is not defined)/, '$2$3');
if (!errposition) {
errposition = source.match(/([\d]+\.)?\d+/g)[0];
}
var offset = Array(Math.max(0, iRuner(-1, source.split('\n')).indexOf(errposition) + 1 + errposition.length / 2 >> 0)).join(' ')
console.warn(
source.replace(errposition, '%c' + errposition + '%c')
+ '\n'
+ offset
+ '^\n'
+ offset
+ '╰─ %c '
+ stack,
"color: #deb887;",
"color: none;",
"color: #deb887;"
);
}
/**
* 隐藏属性格式化输出
* @param {Array|String} data 如果是String 'attr[,|;]attr'
* @param {String} split 默认为 '/'
*/
function StringMap(data, split) {
if (isString(data)) {
data = iSplit(data, split || /,|\||;/)
}
var list = [], map = {};
each(data, function (k, v, host, map) {
map[k] = v;
host.push(iStringFormat('{!{0},,{0}}', v));
}, list, this.map = map);
this.$mode = list.join(this.split = (split || '/'));
this.$data = {};
}
StringMap.prototype.data = function (mode) {
if (isNil(mode)) return this.$data;
let ret = {};
each(mode.split(this.split), function (k, v, map, ret) {
ret[map[k]] = v;
}, this.map, ret)
return ret;
}
StringMap.prototype.toString = function (data) {
data = data || {};
merge(data, this.$data);
merge(this.$data, data, true);
return iStringFormat(this.$mode, data)
}
module.exports = {
format: iStringFormat,
between,
StringMap
}