nornj
Version:
A powerful template engine that can works with React, JSX enhancement or alternative tools.
444 lines (359 loc) • 12.9 kB
JavaScript
import nj from '../core';
import * as tools from '../utils/tools';
import * as tranElem from '../transforms/transformElement';
const {
preAsts
} = nj;
const SPLIT_FLAG = '_njParam';
const TEXT_CONTENT = ['style', 'script', 'textarea', 'xmp', nj.textTag];
const {
OMITTED_CLOSE_TAGS
} = tranElem; //Compile string template
export function compileStringTmpl(tmpl) {
const tmplKey = tmpl.toString(); //Get unique key
let ret = preAsts[tmplKey];
const {
outputH,
tmplRule,
onlyParse,
fileName,
isExpression,
isCss
} = this;
if (!ret) {
//If the cache already has template data, direct return the template.
const isStr = tools.isString(tmpl),
xmls = isStr ? [tmpl] : tmpl,
l = xmls.length;
let fullXml = '',
isInBrace = false; //Connection xml string
tools.each(xmls, (xml, i) => {
let split = '';
if (i == 0) {
if (isExpression) {
xml = (outputH ? tmplRule.firstChar : '') + tmplRule.startRule + ' ' + xml;
} else if (isCss) {
xml = '<' + tmplRule.extensionRule + 'css style="' + xml;
}
}
if (i < l - 1) {
const last = xml.length - 1,
lastChar = xml[last],
lastChar3 = xml.substr(last - 2),
isAccessor = lastChar === '#',
isSpread = lastChar3 === '...';
if (isInBrace) {
isInBrace = !tmplRule['incompleteEnd' + (isInBrace === 'isR' ? 'R' : '')].test(xml);
}
if (!isInBrace) {
if (tmplRule.incompleteStartR.test(xml)) {
isInBrace = 'isR';
} else {
isInBrace = tmplRule.incompleteStart.test(xml);
}
}
if (isAccessor) {
xml = xml.substr(0, last);
} else if (isSpread) {
xml = xml.substr(0, last - 2);
}
split = (isAccessor ? '#' : isSpread ? '...' : '') + SPLIT_FLAG + i;
if (!isInBrace) {
split = tmplRule.startRule + split + tmplRule.endRule;
}
}
if (i == l - 1) {
if (isExpression) {
xml += ' ' + tmplRule.endRule + (outputH ? tmplRule.lastChar : '');
} else if (isCss) {
xml += '" />';
}
}
fullXml += xml + split;
}, true); //Merge all include tags
const includeParser = nj.includeParser;
if (includeParser) {
fullXml = includeParser(fullXml, fileName, tmplRule);
}
fullXml = _formatAll(fullXml, tmplRule);
if (!outputH) {
if (nj.textMode) {
fullXml = '<' + nj.textTag + '>' + fullXml + '</' + nj.textTag + '>';
}
if (nj.noWsMode) {
fullXml = '<' + nj.noWsTag + '>' + fullXml + '</' + nj.noWsTag + '>';
}
} //Resolve string to element
ret = _checkStringElem(fullXml, tmplRule, outputH);
tools.defineProp(ret, '_njParamCount', {
value: l - 1
}); //Save to the cache
preAsts[tmplKey] = ret;
}
let tmplFn;
if (!onlyParse) {
let params;
const args = arguments,
paramCount = ret._njParamCount;
if (paramCount > 0) {
params = {};
tools.defineProp(params, '_njParam', {
value: true
});
for (let i = 0; i < paramCount; i++) {
params[SPLIT_FLAG + i] = args[i + 1];
}
}
tmplFn = params ? function () {
return tmplMainFn.apply(this, tools.arrayPush([params], arguments));
} : function () {
return tmplMainFn.apply(this, arguments);
};
tools.defineProps(tmplFn, {
_njTmpl: {
value: ret
},
_njTmplKey: {
value: tmplKey
}
});
const tmplMainFn = nj['compile' + (outputH ? 'H' : '')](tmplFn, tmplKey, null, null, tmplRule);
} else {
tmplFn = {
_njTmpl: ret,
_njTmplKey: tmplKey
};
}
return tmplFn;
}
function _createCurrent(elemName, parent) {
const current = {
elem: [],
elemName,
parent
};
parent.elem.push(current.elem);
return current;
}
function _setTextAfter(textAfter, current) {
textAfter && textAfter !== '' && _setText(textAfter, current.elem);
} //Resolve string to element
function _checkStringElem(xml, tmplRule, outputH) {
const root = [],
pattern = tmplRule.checkElem;
let current = {
elem: root,
elemName: 'root',
parent: null
},
parent = null,
matchArr,
inTextContent,
omittedCloseElem = null;
while (matchArr = pattern.exec(xml)) {
const textBefore = matchArr[1],
elem = matchArr[2],
elemName = matchArr[3],
elemParams = matchArr[4];
let textAfter = matchArr[5]; //处理上一次循环中的可省略闭合标签
if (omittedCloseElem) {
const [_elem, _elemName, _elemParams, _textAfter] = omittedCloseElem;
const isEx = elem ? tranElem.isExAll(elemName, tmplRule) : false;
if (isEx && !isEx[1] && (tranElem.isPropS(elemName, tmplRule) || tranElem.isStrPropS(elemName, tmplRule) || tranElem.isParamsEx(isEx[3]) || tranElem.exCompileConfig(isEx[3]).isDirective)) {
parent = current;
current = _createCurrent(_elemName, parent);
_setElem(_elem, _elemName, _elemParams, current.elem, null, tmplRule, outputH);
} else {
_setSelfCloseElem(_elem, _elemName, _elemParams, current.elem, tmplRule, outputH);
}
_setTextAfter(_textAfter, current);
omittedCloseElem = null;
} //Text before tag
if (textBefore && textBefore !== '') {
_setText(textBefore, current.elem);
} //Element tag
if (elem) {
if (elem !== '<') {
if (elem.indexOf('<!') === 0) {
//一些特殊标签当做文本处理
_setText(elem, current.elem);
} else {
const isEx = tranElem.isExAll(elemName, tmplRule);
if (elemName[0] === '/') {
//Close tag
if (elemName.substr(1).toLowerCase() === inTextContent) {
//取消纯文本子节点标记
inTextContent = null;
}
if (isEx || !inTextContent) {
const cName = current.elemName;
if (cName.indexOf(SPLIT_FLAG) < 0 ? elemName === '/' + cName : elemName.indexOf(SPLIT_FLAG) > -1 || elemName === '//') {
//如果开始标签包含SPLIT_FLAG,则只要结束标签包含SPLIT_FLAG就认为该标签已关闭
current = current.parent;
}
} else {
_setText(elem, current.elem);
}
} else if (elem[elem.length - 2] === '/') {
//Self close tag
if (isEx || !inTextContent) {
_setSelfCloseElem(elem, elemName, elemParams, current.elem, tmplRule, outputH);
} else {
_setText(elem, current.elem);
}
} else {
//Open tag
if (isEx || !inTextContent) {
if (!inTextContent && OMITTED_CLOSE_TAGS[elemName.toLowerCase()]) {
//img等可不闭合标签
omittedCloseElem = [elem, elemName, elemParams, textAfter];
} else {
const elemNameL = elemName.toLowerCase();
if (TEXT_CONTENT.indexOf(elemNameL) > -1) {
//标记该标签为纯文本子节点
inTextContent = elemNameL;
}
parent = current;
current = _createCurrent(elemName, parent);
_setElem(elem, elemName, elemParams, current.elem, null, tmplRule, outputH);
}
} else {
_setText(elem, current.elem);
}
}
}
} else {
//单独的"<"和后面的文本拼合在一起
if (textAfter == null) {
textAfter = '';
}
textAfter = elem + textAfter;
}
} //Text after tag
!omittedCloseElem && _setTextAfter(textAfter, current);
} //处理最后一次循环中遗留的可省略闭合标签
if (omittedCloseElem) {
const [_elem, _elemName, _elemParams, _textAfter] = omittedCloseElem;
_setSelfCloseElem(_elem, _elemName, _elemParams, current.elem, tmplRule, outputH);
_setTextAfter(_textAfter, current);
}
return root;
}
const LT_GT_LOOKUP = {
'<': '_njLt_',
'>': '_njGt_'
};
const REGEX_LT_GT = />|</g;
function _formatAll(str, tmplRule) {
const commentRule = tmplRule.commentRule;
return str.replace(new RegExp('<!--' + commentRule + '[\\s\\S]*?' + commentRule + '-->', 'g'), '').replace(new RegExp('([\\s]+:[^\\s=>]+=((\'[^\']+\')|("[^"]+")))|(' + tmplRule.braceParamStr + ')', 'g'), (all, g1, g2, g3, g4, g5) => (g1 ? g1 : g5).replace(REGEX_LT_GT, match => LT_GT_LOOKUP[match]));
}
function _transformToEx(isStr, elemName, elemParams, tmplRule) {
return tmplRule.extensionRule + (isStr ? 'strProp' : 'prop') + ' ' + tmplRule.startRule + `'` + elemName.substr((isStr ? tmplRule.strPropRule.length : 0) + tmplRule.propRule.length) + `'` + tmplRule.endRule + elemParams;
} //Set element node
function _setElem(elem, elemName, elemParams, elemArr, bySelfClose, tmplRule, outputH) {
let ret, paramsEx;
const fixedExTagName = tranElem.fixExTagName(elemName, tmplRule);
if (fixedExTagName) {
elemName = fixedExTagName;
}
if (tranElem.isEx(elemName, tmplRule, true)) {
ret = elem.substring(1, elem.length - 1);
if (fixedExTagName) {
ret = tmplRule.extensionRule + tools.lowerFirst(ret);
}
const retS = _getSplitParams(ret, tmplRule, outputH);
ret = retS.elem;
paramsEx = retS.params;
} else if (tranElem.isStrPropS(elemName, tmplRule)) {
ret = _transformToEx(true, elemName, elemParams, tmplRule);
} else if (tranElem.isPropS(elemName, tmplRule)) {
ret = _transformToEx(false, elemName, elemParams, tmplRule);
} else {
const retS = _getSplitParams(elem, tmplRule, outputH);
ret = retS.elem;
paramsEx = retS.params;
}
if (bySelfClose) {
const retC = [ret];
if (paramsEx) {
retC.push(paramsEx);
}
elemArr.push(retC);
} else {
elemArr.push(ret);
if (paramsEx) {
elemArr.push(paramsEx);
}
}
}
const REGEX_EX_ATTR = /([^\s-$.]+)(([$.][^\s-$.]+)*)((-[^\s-$.]+([$.][^\s-$.]+)*)*)/; //Extract split parameters
function _getSplitParams(elem, tmplRule, outputH) {
const {
extensionRule,
startRule,
endRule,
firstChar,
lastChar,
spreadProp,
directives
} = tmplRule;
let paramsEx; //Replace the parameter like "{...props}".
elem = elem.replace(spreadProp, (all, g1, propR, g3, prop) => {
if (propR) {
prop = propR;
}
if (!paramsEx) {
paramsEx = [extensionRule + 'props'];
}
paramsEx.push([extensionRule + 'spread ' + (propR ? firstChar : '') + startRule + prop.replace(/\.\.\./, '') + endRule + (propR ? lastChar : '') + '/']);
return ' ';
}); //Replace the parameter like "#show={false}".
elem = elem.replace(directives, (all, g1, g2, g3, g4, g5, g6, key, hasColon, hasEx, name, hasEqual, value) => {
if (hasEx == null) {
return all;
}
if (!paramsEx) {
paramsEx = [extensionRule + 'props'];
}
let args, modifiers;
name = name.replace(REGEX_EX_ATTR, (all, name, modifier, g3, arg) => {
if (arg) {
args = arg.substr(1).split('-').map(item => {
let argStr;
let modifierStr = '';
const strs = item.split(/[$.]/);
strs.forEach((str, i) => {
if (i == 0) {
argStr = `name:'${str}'` + (i < strs.length - 1 ? ',' : '');
} else {
modifierStr += `'${str}'` + (i < strs.length - 1 ? ',' : '');
}
});
return '{' + argStr + (modifierStr != '' ? 'modifiers:[' + modifierStr + ']' : '') + '}';
});
}
if (modifier) {
modifiers = modifier.substr(1).split(/[$.]/).map(item => `'${item}'`);
}
return name;
});
const exPreAst = [extensionRule + name + ' _njIsDirective' + (args ? ' arguments="' + firstChar + startRule + '[' + args.join(',') + ']' + endRule + lastChar + '"' : '') + (modifiers ? ' modifiers="' + startRule + '[' + modifiers.join(',') + ']' + endRule + '"' : '') + (hasEqual ? '' : ' /')];
hasEqual && exPreAst.push((hasColon ? (outputH ? firstChar : '') + startRule + ' ' : '') + tools.clearQuot(value) + (hasColon ? ' ' + endRule + (outputH ? lastChar : '') : ''));
paramsEx.push(exPreAst);
return ' ';
});
return {
elem,
params: paramsEx
};
} //Set self close element node
function _setSelfCloseElem(elem, elemName, elemParams, elemArr, tmplRule, outputH) {
if (/\/$/.test(elemName)) {
elemName = elemName.substr(0, elemName.length - 1);
}
_setElem(elem, elemName, elemParams, elemArr, true, tmplRule, outputH);
} //Set text node
function _setText(text, elemArr) {
elemArr.push(text);
}