nornj
Version:
A powerful template engine that can works with React, JSX enhancement or alternative tools.
315 lines (257 loc) • 10 kB
JavaScript
import nj from '../core';
import * as tools from '../utils/tools';
import '../helpers/filter'; //Get compiled property
const REGEX_JS_PROP = /('[^']*')|("[^"]*")|(-?[0-9][0-9]*(\.\d+)?)|true|false|null|undefined|Object|Array|Math|Date|JSON|(([a-zA-Z_$#@])([a-zA-Z_$\d]*))/;
const REGEX_REPLACE_CHAR = /_njQs(\d+)_/g;
const REGEX_REPLACE_SET = /_njSet_/;
function _replaceStr(prop, innerQuotes) {
return prop.replace(REGEX_REPLACE_CHAR, (all, g1) => innerQuotes[g1]);
}
function _compiledProp(prop, innerBrackets, innerQuotes, source) {
const ret = tools.obj();
const propO = prop; //If there are vertical lines in the property,then use filter
if (prop.indexOf('|') >= 0) {
const filters = [];
let filtersTmp;
filtersTmp = prop.split('|');
prop = filtersTmp[0].trim(); //Extract property
filtersTmp = filtersTmp.slice(1);
tools.each(filtersTmp, filter => {
filter = filter.trim();
if (filter === '') {
return;
}
const retF = _getFilterParam(filter),
filterObj = tools.obj(),
filterName = retF[0].trim(); //Get filter name
if (filterName) {
const paramsF = retF[1]; //Get filter param
//Multiple params are separated by commas.
if (paramsF != null) {
const params = [];
tools.each(innerBrackets[paramsF].split(','), p => {
if (p !== '') {
params[params.length] = _compiledProp(p.trim(), innerBrackets, innerQuotes, source);
}
}, true);
filterObj.params = params;
}
filterObj.name = filterName;
filters.push(filterObj);
}
}, true);
ret.filters = filters;
} //替换字符串值
prop = _replaceStr(prop, innerQuotes); //Extract the parent data path
if (prop.indexOf('../') === 0) {
let n = 0;
prop = prop.replace(/\.\.\//g, function () {
n++;
return '';
});
ret.parentNum = n;
} //Extract the js property
if (prop !== '') {
const matchProp = REGEX_JS_PROP.exec(prop);
const hasAccessor = matchProp[6] === '#';
ret.name = hasAccessor ? matchProp[7] : matchProp[0];
if (!matchProp[5]) {
//Sign the parameter is a basic type value.
ret.isBasicType = true;
}
if (hasAccessor) {
ret.isAccessor = true;
}
ret.name = ret.name.replace(REGEX_REPLACE_SET, () => {
ret.hasSet = true;
return '';
});
} else {
ret.isEmpty = true;
}
return ret;
} //Get filter param
function _getFilterParam(obj) {
return obj.split(`'bracket_`);
} //Extract replace parameters
const REGEX_LT_GT = /_nj(L|G)t_/g;
const LT_GT_LOOKUP = {
_njLt_: '<',
_njGt_: '>'
};
const REGEX_QUOTE = /"[^"]*"|'[^']*'/g;
const REGEX_OPERATORS_ESCAPE = /\*|\||\/|\.|\?|\+/g;
const SP_FILTER_LOOKUP = {
'||': 'or',
'..': 'rOpe',
'..<': 'rLt'
};
let REGEX_SP_FILTER;
function createFilterAlias(name, alias) {
if (name && alias) {
SP_FILTER_LOOKUP[name] = alias;
}
REGEX_SP_FILTER = new RegExp('[\\s]+((' + Object.keys(SP_FILTER_LOOKUP).map(o => {
return o.replace(REGEX_OPERATORS_ESCAPE, match => '\\' + match);
}).join('|') + ')[\\s]+)', 'g');
}
createFilterAlias();
const FN_FILTER_LOOKUP = {
')': ')_(',
']': ']_('
};
const REGEX_FN_FILTER = /(\)|\]|\.([^\s'"._#()|]+))[\s]*\(/g;
const REGEX_SPACE_S_FILTER = /([(,|])[\s]+/g;
const REGEX_PROP_FILTER = /\.([a-zA-Z_$#@][a-zA-Z_$\d]*)/g;
const REGEX_ARRPROP_FILTER = /([^\s([,])(\[)/g;
const ARR_OBJ_FILTER_LOOKUP = {
'[': 'list(',
']': ')',
'{': 'obj(',
'}': ')'
};
const REGEX_ARR_OBJ_FILTER = /\[|\]|\{|\}/g; //const REGEX_OBJKEY_FILTER = /([(,][\s]*)([^\s:,'"()|]+):/g;
const REGEX_SET_FILTER = /^[\s]*set[\s]+|([(,])[\s]*set[\s]+/g;
const REGEX_BRACKET_FILTER = /^[\s]*([(]+)|([(,])[\s]*([(]+)/g;
const NOT_OPERATORS = ['../'];
const REGEX_NEGATIVE = /-[0-9]/;
const BEGIN_CHARS = ['', '(', '[', ','];
const OPERATORS = ['+=', '+', '-[0-9]', '-', '**', '*', '%%', '%', '===', '!==', '==', '!=', '<=>', '<=', '>=', '=', '..<', '<', '>', '&&', '||', '?:', '?', ':', '../', '..', '/'];
let REGEX_OPERATORS;
function createRegexOperators(operator) {
if (operator) {
let insertIndex = 0;
OPERATORS.forEach((o, i) => {
if (o.indexOf(operator) >= 0) {
insertIndex = i + 1;
}
});
OPERATORS.splice(insertIndex, 0, operator);
}
REGEX_OPERATORS = new RegExp(OPERATORS.map(o => {
return o.replace(REGEX_OPERATORS_ESCAPE, match => '\\' + match);
}).join('|'), 'g');
}
createRegexOperators();
function _getProp(matchArr, innerQuotes, i, addSet) {
let prop = matchArr[2].trim();
const item = [matchArr[0], matchArr[1], null, true];
if (i > 0) {
item[3] = false; //Sign not contain all of placehorder
}
if (addSet) {
prop = 'set ' + prop;
} //替换特殊过滤器名称并且为简化过滤器补全"|"符
prop = prop.replace(REGEX_LT_GT, match => LT_GT_LOOKUP[match]).replace(REGEX_QUOTE, match => {
innerQuotes.push(match);
return '_njQs' + (innerQuotes.length - 1) + '_';
}).replace(REGEX_OPERATORS, (match, index) => {
if (REGEX_NEGATIVE.test(match)) {
if (index > 0 && BEGIN_CHARS.indexOf(prop[index - 1].trim()) < 0) {
//Example: 123-456
return match.split('-').join(' - ');
} else {
//Example: -123+456
return match;
}
} else {
return NOT_OPERATORS.indexOf(match) < 0 ? ` ${match} ` : match;
}
}).replace(REGEX_SP_FILTER, (all, g1, match) => ' ' + SP_FILTER_LOOKUP[match] + ' ').replace(REGEX_PROP_FILTER, (all, g1) => {
const startWithHash = g1[0] === '#';
if (startWithHash) {
g1 = g1.substr(1);
}
const lastCharIndex = g1.length - 1,
endWithUnderline = lastCharIndex > 0 && g1[lastCharIndex] === '_';
return (startWithHash ? '#' : '.') + `('` + (endWithUnderline ? g1.substr(0, lastCharIndex) : g1) + `')` + (endWithUnderline ? '_' : '');
}).replace(REGEX_ARRPROP_FILTER, (all, g1, g2) => g1 + '.(').replace(REGEX_ARR_OBJ_FILTER, match => ARR_OBJ_FILTER_LOOKUP[match]).replace(REGEX_SET_FILTER, (all, g1) => (g1 ? g1 : '') + '_njSet_').replace(REGEX_BRACKET_FILTER, (all, g1, g2, g3) => (g2 ? g2 : '') + (g2 ? g3 : g1).replace(/[(]/g, 'bracket(')) //.replace(REGEX_OBJKEY_FILTER, (all, g1, g2) => g1 + ' \'' + g2 + '\' : ')
.replace(REGEX_SPACE_S_FILTER, (all, match) => match).replace(REGEX_FN_FILTER, (all, match, g1) => !g1 ? FN_FILTER_LOOKUP[match] : `.('` + g1 + `')_(`);
item[2] = prop.trim();
return item;
}
function _getReplaceParam(obj, tmplRule, innerQuotes, hasColon, addSet) {
const pattern = tmplRule.replaceParam;
let matchArr,
ret,
i = 0;
if (!hasColon) {
while (matchArr = pattern.exec(obj)) {
if (!ret) {
ret = [];
}
const startRuleR = matchArr[2];
ret.push(_getProp([matchArr[0], startRuleR ? startRuleR : matchArr[5], startRuleR ? matchArr[3] : matchArr[6]], innerQuotes, i, addSet));
i++;
}
} else {
matchArr = [obj, tmplRule.startRule, obj];
ret = [_getProp(matchArr, innerQuotes, i, addSet)];
}
return ret;
}
const REGEX_INNER_BRACKET = /\(([^()]*)\)/g;
const REGEX_FIX_OPERATOR_1 = /([!]+)((-?[0-9][0-9]*(\.\d+)?|[^\s,|'=]+)('bracket_\d+)?([._#]'bracket_\d+)*)/g;
const REGEX_FIX_OPERATOR = /[\s]+([^\s(),|"']+)[\s]+((-?[0-9][0-9]*(\.\d+)?|[^\s,|']+)('bracket_\d+)?([._#]'bracket_\d+)*)/g;
const REGEX_SPACE_FILTER = /[(,]/g;
const REGEX_FIX_FILTER = /(\|)?(((\.+|_|#+)'bracket_)|[\s]+([^\s._#|]+[\s]*'bracket_))/g;
function _fixOperator(prop, innerBrackets) {
prop = prop.replace(REGEX_FIX_OPERATOR_1, function () {
const args = arguments;
innerBrackets.push(_fixFilter(args[2]));
return args[1] + `'bracket_` + (innerBrackets.length - 1);
});
return _fixFilter(prop.replace(REGEX_FIX_OPERATOR, function () {
const args = arguments;
innerBrackets.push(_fixFilter(args[2]));
return ' ' + args[1] + `'bracket_` + (innerBrackets.length - 1);
}));
}
function _fixFilter(prop) {
return (' ' + prop).replace(REGEX_SPACE_FILTER, all => all + ' ').replace(REGEX_FIX_FILTER, (all, g1, g2, g3, g4, g5) => g1 ? all : ' | ' + (g3 ? g3 : g5)).trim();
}
function _replaceInnerBrackets(prop, innerBrackets) {
const propR = prop.replace(REGEX_INNER_BRACKET, (all, s1) => {
innerBrackets.push(_fixOperator(s1, innerBrackets));
return `'bracket_` + (innerBrackets.length - 1);
});
if (propR !== prop) {
return _replaceInnerBrackets(propR, innerBrackets);
} else {
return _fixOperator(propR, innerBrackets);
}
} //Get compiled parameter
export function compiledParam(value, tmplRule, hasColon, onlyKey, addSet) {
const ret = tools.obj(),
isStr = tools.isString(value);
let strs = isStr ? !hasColon ? value.split(tmplRule.replaceSplit) : ['', ''] : [value],
props = null,
isAll = false; //此处指替换符是否占满整个属性值;若无替换符时为false
if (isStr) {
//替换插值变量以外的文本中的换行符
strs = strs.map(str => str.replace(/\n/g, '_njNl_').replace(/\r/g, ''));
} //If have placehorder
if (strs.length > 1) {
const innerQuotes = [];
const params = _getReplaceParam(value, tmplRule, innerQuotes, hasColon, addSet);
props = [];
tools.each(params, param => {
const retP = tools.obj(),
innerBrackets = [];
isAll = param[3] ? param[0] === value : false; //If there are several curly braces in one property value, "isAll" must be false.
const prop = _replaceInnerBrackets(param[2], innerBrackets);
retP.prop = _compiledProp(prop, innerBrackets, innerQuotes, value); //To determine whether it is necessary to escape
retP.escape = param[1] !== tmplRule.firstChar + tmplRule.startRule;
props.push(retP);
}, true);
}
ret.props = props;
ret.strs = strs;
ret.isAll = isAll;
ret.onlyKey = onlyKey;
return ret;
}
tools.assign(nj, {
createFilterAlias,
createRegexOperators
});