zj-element
Version:
基于ElementUI的VUE组件——Жидзин(Zidjin)系列组件库。
220 lines (189 loc) • 9.26 kB
JavaScript
/** x-format-string V1.0 自动格式化字符串,支持双花括号 20230213 by Sieyoo, 赵向明 */
import XTakeAt from "./x-take-at.js"
const XTemplateString = function() {};
/**
* function XTemplateString::format 对模板字符串进行编译,格式化为结果字符串
* @param {object} item 给定需要格式化的Key-Value对象。
* @param {string} text 要格式化的字符串文本
* @desc
* @return {string} 格式化完成后字符串
* @example
* 模板语法糖:
* 1、基础版——直接引用值。语法:{{key}}
* 例子:
* param={category_id: 35, category_value: "杭州西湖区"};
* str="类别ID:{{category_id}},类别值:{{category_value}}。";
* XTemplateString.format(param, str);
* 结果 ==> "类别ID:35,类别值:杭州西湖区。"
* 2、高级版——管道选项值。即指定列表对象、标签键、值键,找到引用值。语法:{{key:key|options[labelKey]}}
* 例子:
* param={category_id: 35, category_list: [{value: 27, label: "杭州上城区"}, {value: 35, label: "杭州西湖区"}]};
* str="类别:{{ value: category_id | category_list[label] }}"
* XTemplateString.format(param, str);
* 结果 ==> "类别:杭州西湖区。"
* 3、高级版——强制转换值。即指定来源键、目标键,生成数组对象。语法:{{(Array<targetKey:sourceKey>)key<splitFlag>}}
* 例子:
* param={photo_list: [{link: '/images/a.jpg', label: "杭州上城区"}, {link: '/images/b.jpg', label: "杭州西湖区"}]};
* str="{{ (Array< url: link, text: label >)photo_list }}"
* str2="{{ (Array< :link >)photo_list }}"
* XTemplateString.format(param, str);
* 结果 ==> "[{url: '/images/a.jpg', text: "杭州上城区"}, {url: '/images/b.jpg', text: "杭州西湖区"}]"
* XTemplateString.format(param, str2);
* 结果 ==> "['/images/a.jpg', '/images/b.jpg']"
*/
XTemplateString.format = function(item, text){
// console.log("XTemplateString.format:", item, text)
if(!text){
return '';
}
// typeof(formData[it.field]) === 'object' ? JSON.stringify(formData[it.field]) : formData[it.field]
// 暂时弃用,小程序不支持eval语法
// let expression = text.replace(/(\{\{(.*?)\}\})/g, "$${item.$2}");
// let result = eval('`'+expression+'`').replaceAll('"null"', '""');
let result = text;
if(typeof text === 'object'){
result = JSON.stringify(text)
}
const re = new RegExp("(\{\{(.*?)\}\})");
while(re.test(result)){
const key = re.exec(result)[2];
let value = XTakeAt.getValue(item, key, ''); // 注意:如果key是高级模版语法,是匹配不出value值的。
// const logic = XTemplateString.matchLogic(item, key); // 逻辑运算处理
const label = XTemplateString.matchOptions(item, key); // 管道选项处理
const force = XTemplateString.matchForceType(item, key); // 强制转换处理
if(typeof(value) === 'string'){
value = value.replace(/"/g, '\\"')
}
// console.log("XTemplateString.format k-v:", key, '=', {value: value}, '==>', label);
// 强制转换时要改变类型,所以字符串需要替换掉两头的""双引号
result = force ? result.replace(/("\{\{(.*?)\}\})"/, force) : result.replace(re, label || value );
}
// 因为JSON反序列化不支持\r\n,所以只能强制替换掉
if( typeof result === 'string'){
result = result
// .replace(/\\/g, '\\\\') //这个千万不能写,会导致\"都失效的
.replace(/\n/g, '\\n')
.replace(/\r/g, '\\r')
.replace(/\t/g, '\\t')
}
if(typeof text === 'object'){
// 注:如果json中混入html语言或\n换行符等异物,会导致反解析失败
try {
result = JSON.parse(result);
} catch (err) {
console.warn("【模块字符串】转换JSON时失败!", err, "result =>", result, {result});
result = text;
}
}
// console.log("XTemplateString.format end:", result)
return result;
}
// 逻辑运算值。
XTemplateString.matchLogic = function(item, expression_string){
// console.log("XTemplateString.matchLogic:", expression_string, item)
if (!expression_string){
return null;
}
const re = (/([_\w]*?)\s*:?\s*([_\w]*?)\s*\|\s*([_\w]*?)\[\s*([_\w]*?)\s*\]/);
// 检查是否
if(!re.test(expression_string))
return null;
}
// 管道选项值。作用:"value:category_id|category_list[label]" ==> "杭州西湖区"
XTemplateString.matchOptions = function(item, expression_string){
// console.log("XTemplateString.matchOptions:", expression_string, item)
if (!expression_string){
return null;
}
const re = (/([_\w]*?)\s*:?\s*([_\w]*?)\s*\|\s*([_\w]*?)\[\s*([_\w]*?)\s*\]/);
// 检查是否
if(!re.test(expression_string))
return null;
// console.log("XTemplateString.matchOptions re:", re.exec(expression_string));
const match = re.exec(expression_string);
const itemKey = match[2];
const valueKey = match[1] || itemKey;
const optionsKey = match[3];
const labelKey = match[4];
const options = XTakeAt.getValue(item, optionsKey, ''); // item[optionsKey];
const value = XTakeAt.getValue(item, itemKey, ''); // item[itemKey];
if(!options || !labelKey || !valueKey){
return value;
}
// 查找到当前匹配的对象,
let current = options.find(it => it[valueKey] == value);
let label = current && current[labelKey];
// console.log("XTemplateString.matchOptions label 2:", {key: label});
if(!label){
return value;
}
return label;
}
// 强制转换值。作用:(Array<url: url, text: uid>)files ==> [{"url": "/images/xx.jpg", "text": "标题"}]
XTemplateString.matchForceType = function(item, expression_string){
// console.log("XTemplateString.matchForceType:", expression_string, item)
if (!expression_string){
return null;
}
// const re = (/\(\s*([\w]*?)\s*<\s*([_:,\s\w]*?)\s*>\s*\)\s*([_\w]*?)\s*$/); // 不支持<,>分隔符
const re = (/\(\s*([\w]*?)\s*<\s*([_:,\s\w]*?)\s*>\s*\)\s*([_\w]*)\s*(?:\<(.*?)\>)?\s*/);
// 检查是否
if(!re.test(expression_string))
return null;
// console.log("XTemplateString.matchForceType re:", re.exec(expression_string));
const match = re.exec(expression_string);
const type = match[1];
const pairs = match[2];
const key = match[3];
const splitFlag = match[4];
// console.log("matchForceType type:(", type, ") pairs:(", pairs, ") key:(", key, ")");
const value = XTakeAt.getValue(item, key, '');
// console.log("matchForceType value:", value)
// 边界检查:如果列表值为空,返回空列表!
if(!value || !value.length || !value[0]){
return "[]";
}
// 边界检查:如果匹配不完整,退出!
if(!type || type!=='Array' || !key){
return typeof value === 'object' && JSON.stringify(value) || value;
}
// 生成转换用的键值对。
let pairList = pairs.split(',').map(it => {
// console.log("matchForceType it:", it)
let kv = it.split(':');
return {
targetKey: kv.length>=1 ? kv[0].trim() : '',
sourceKey: kv.length>=2 ? kv[1].trim() : kv[0] || '', // 功能细节:如果没有传sourceKey,则代表来源键和目标键同名!
}
});
// console.log("matchForceType pairList:", pairList)
let sourceValue = value;
// 边界检查:如为字符串,逗号分隔为数组
if(typeof sourceValue === "string"){
sourceValue = sourceValue.split(splitFlag || ';'); // 问:能不能指定分隔符?by Sieyoo
}
// 边界检查:如果不是数组,退出!
if(!Array.isArray(sourceValue) ){
return typeof value === 'object' && JSON.stringify(value) || value;
}
// console.log("matchForceType sourceValue:", sourceValue)
let result = sourceValue.map(item => {
// 如果没有目标键,则返回一维数组,即数组内是字符串
if(pairList.length && !pairList[0].targetKey){
return item[pairList[0].sourceKey] || item || '';
}
// 如果来源值是一维(字符串)数组,则仅返回第一组键值对(可解释为非贪婪模式?)
if(pairList.length && typeof item === "string"){
return {[pairList[0].sourceKey]: item};
}
// 生成新的键值对
let newObj = {}
pairList.forEach(it => {
newObj[it.targetKey] = item[it.sourceKey] || '';
});
return newObj;
})
// console.log("matchForceType result:", result)
return JSON.stringify(result);
}
export default XTemplateString;