UNPKG

magix-composer

Version:

compile html, style and javascript files into javascript

1,279 lines (1,252 loc) 60 kB
/* <--循环支持isLast isFirst--> {{each list as value index isLast isFirst}} {{/each}} <--节点属性可以使用对象展开,展开操作可以使用*或...操作符--> <div {{*attrs}} {{...attrs}}></div> <--可以直接引用生成的虚拟节点--> {{&virtualNodes}} <--循环写法--> {{each list as value}} <div>{{=value}}</div> {{/each}} or <div qk:each="{{list as value}}">{{=value}}</div> {{forin list as value}} <div>{{=value}}</div> {{/forin}} or <div qk:forin="{{list as value}}">{{=value}}</div> {{for(let i=0;i<10;i++)}} <div>{{=i}}</div> {{/for}} or <div qk:for="{{let i=;i<10;i++}}">{{=i}}</div> */ let htmlParser = require('./html-parser'); let tmplCmd = require('./tmpl-cmd'); let configs = require('./util-config'); let artExpr = require('./tmpl-art-ctrl'); let { quickDirectTagName, quickGroupTagName, quickDirectCodeAttr, quickSpreadAttr, quickAutoAttr, quickOpenAttr, quickCloseAttr, quickEachAttr, quickElseIfAttr, quickForAttr, quickIfAttr, quickForInAttr, quickDeclareAttr, quickConditionReg, quickLoopReg, quickElseAttr, tmplStoreIndexKey, tmplTempRealStaticKey, artCommandReg, tmplGroupTag, tmplCondPrefix, tmplGroupKeyAttr, tmplGroupUseAttr, tmplVarTempKey, quickSourceArt, tmplMxViewParamKey, tmplStaticKey } = require('./util-const'); let utils = require('./util'); let util = require('util'); let regexp = require('./util-rcache'); let slog = require('./util-log'); let attrMap = require('./html-attrs'); let tmplUnescape = require('html-entities-decoder'); let md5 = require('./util-md5'); let chalk = require('chalk'); let viewIdReg = /\x1f/g; let artCtrlReg = /(?:<%'(\d+)\x11([^\x11]+)\x11'%>)?<%([@=:&])?([\s\S]+?)%>/g; let inReg = /\(([\s\S]+?)\s*,\s*([^),]+),\s*([^),]+),\s*([^),]+),\s*(1|-1)\)\s*in\s+([\S\s]+)/; let mathcer = /<%([@=*]|\.{3})?([\s\S]*?)%>|$/g; let escapeSlashRegExp = /\\|'/g; let escapeBreakReturnRegExp = /\r|\n/g; let suffixReg = /\+'';\s*/g; let endReg = /;\s*$/; let condPlus = /\+''\+/g; let tagHReg = /\x03\d+\x03/g; let tmplCommandAnchorReg = /\x07\d+\x07/g; let ifExtractReg = /^\s*(?:for|if)\s*\(([\s\S]+?)\)\s*;?\s*$/; let commaExprReg = /(?:,''\)|(%>'));/g; let directReg = /\{\{&[\s\S]+?\}\}/g; let spreadAttrsReg = /\{\{(?:\*|\.{3})[\s\S]+?\}\}/g; let condPrefix = /^\x1c\d+\x1c/; let tagReg = /<(\/?)([^>\s]+)[^>]*>/g; let matchedTagReg = /(<([^>\s\/]+)[^>]*>)([^<>]*?)(<\/\2>)/g; let lastCloseReg = />([^>]*)$/; let condEscapeReg = /^((?:\x07\d+\x07)+\s*\\*?)\\\?/; let tmplFnParams = ['$n', '$eu', '$_ref', '$i', '$eq', '$_is_array']; let tmplRadioOrCheckboxKey = 'tmpl_radio_or_checkbox_names'; let tmplStaticVarsKey = 'tmpl_static_vars_key'; let groupsReg = /(?:^|,)groups(?=,|$)/; let longExpr = /[\.\[\]]/; let storeInnerMatchedTags = (tmpl, store) => { let idx = store[tmplStoreIndexKey] || 0; return tmpl.replace(matchedTagReg, (m, prefix, tag, content, suffix) => { let groups = [prefix, content, suffix]; let returned = ''; for (let g of groups) { let key = '\x03' + idx++ + '\x03'; store[key] = { tag: g == prefix, src: g }; returned += key; } store[tmplStoreIndexKey] = idx; return returned; }); }; let storeHTML = (tmpl, store) => { let idx = store[tmplStoreIndexKey] || 0; return tmpl.replace(tagReg, (m, closed, tag) => { let key = '\x03' + idx++ + '\x03'; store[key] = { tag: closed ? false : true, special: tag == quickDirectTagName || tag == quickGroupTagName, src: m }; store[tmplStoreIndexKey] = idx; return key; }); }; let extractArtAndCtrlFrom = tmpl => { let result = []; //console.log(tmpl); tmpl.replace(artCtrlReg, (match, line, art, operate, ctrl) => { result.push({ origin: match, line, operate, art, ctrl }); }); return result; }; let toFn = (key, tmpl, fromAttr) => { //tmpl = tmpl.replace(/%>\s+<%/g, '%><%'); //console.log(tmpl); let index = 0, hasCtrl = false, hasOut = false, hasCmdOut = false, source = `${key}='`, snippet, preArt = -1, ctrlCount = 0, hasSnippet = false, hasCharSnippet = false, setStart = false, hasVarOut = false, reg = regexp.get(`${regexp.escape(key)}\\+='';+`, 'g'); tmpl.replace(mathcer, (match, operate, content, offset) => { snippet = tmpl.substring(index, offset) .replace(escapeSlashRegExp, `\\$&`) .replace(escapeBreakReturnRegExp, `\\n`); if (snippet) { hasSnippet = hasSnippet || !content || !setStart; hasCharSnippet = hasCharSnippet || !!snippet.trim(); hasOut = true; if (preArt == index) { source += `'')+'`; } } setStart = true; //if (decode) { snippet = tmplUnescape(snippet); //} source += snippet; index = offset + match.length; let ctrl = tmpl.substring(index - match.length + 2 + (operate ? operate.length : 0), index - 2); let artReg = /^'(\d+)\x11([^\x11]+)\x11'$/; let artMatch = ctrl.match(artReg); let art = '', line = -1; ctrl = ctrl.replace(escapeSlashRegExp, `\\$&`).replace(escapeBreakReturnRegExp, `\\n`); if (artMatch) { ctrl = ''; art = artMatch[2]; line = artMatch[1]; } if (operate == '@') { hasOut = true; hasCmdOut = true; hasVarOut = true; //let a = tmplCmd.extractRefContent(content); //console.log(a); //let out = `($_ref[${a.key}]=${a.vars},${a.key})`; let out = `$i($_ref,${content})`; if (configs.debug) { if (preArt == offset) { source += `$__ctrl='<%@${ctrl}%>',${out})+'`; } else { source += `'+($__ctrl='<%@${ctrl}%>',${out})+'`; } } else { source += `'+${out}+'`; } } else if (operate == '=') { hasOut = true; hasCmdOut = true; hasVarOut = true; let safe = ``; if ((!content.startsWith('$eq(') && !content.startsWith('$i(') && !content.startsWith('$eu(') && !content.startsWith('$n('))) { safe = '$n'; } let out = `${safe}(${content})`; if (configs.debug) { if (preArt == offset) { source += `$__ctrl='<%=${ctrl}%>',${out})+'`; } else { source += `'+($__ctrl='<%=${ctrl}%>',${out})+'`; } } else { source += `'+${out}+'`; } } else if (operate == '*' || operate == '...') { hasOut = true; hasCmdOut = true; hasVarOut = true; if (configs.debug) { if (preArt == offset) { source += `$__ctrl='<%${operate}${ctrl}%>',${content})+'`; } else { source += `'+($__ctrl='<%${operate}${ctrl}%>',${content})+'`; } } else { source += `'+${content}+'`; } } else if (content) { if (line > -1) { preArt = index; source += `'+($__line=${line},$__art='{{${art}}}',`; hasVarOut = true; } else { ctrlCount++; if (preArt == offset) { source += `'')+'`; } hasCtrl = true; source += `';`; if (configs.debug) { source += `$__ctrl='<%${ctrl}%>';`; } source += `${content};${key}+='`; } } return match; }); source += `';`; source = source .replace(viewIdReg, `'+$_viewId+'`) .replace(reg, ''); reg = regexp.get(`^${regexp.escape(key)}=''\\+`); source = source .replace(reg, regexp.encode(key + '=')) .replace(suffixReg, ';') .replace(condPlus, '+') .replace(endReg, ''); //console.log(source, reg); //like '($__line=2,$__art=\'{{checked}}\',\'\');$__ctrl=\'<%$$.checked%>\';$$.checked' if (configs.debug && fromAttr && !hasOut && ctrlCount == 1) { source = source.replace(commaExprReg, '$1,') + ')'; } if (ctrlCount > 1 && !hasOut) {//如果超出1条控制语句,即使没有输出,也要认为有输出 hasOut = true; } if (!hasOut || !hasCtrl) { reg = regexp.get(`^${regexp.escape(key)}=(?:'';+)?`); source = source.replace(reg, ''); } return { source, hasOut, hasSnippet, hasCharSnippet, hasVarOut, hasCmdOut, hasCtrl }; }; let serAttrs = (key, value, fromAttr) => { if (value === true) { return { hasOut: true, direct: true, returned: true }; } let { source, hasCtrl, hasOut, hasSnippet, hasCharSnippet, hasCmdOut, hasVarOut } = toFn(key, value, fromAttr); if (hasCtrl && hasOut) { return { direct: false, hasCmdOut, hasCharSnippet, returned: source, hasSnippet, hasVarOut }; } else { return { direct: true, hasCtrl, hasCmdOut, hasCharSnippet, returned: source, hasVarOut }; } }; let getForContent = (cnt, e) => { let fi = extractArtAndCtrlFrom(cnt); if (fi.length > 1 || fi.length < 1) { throw new Error('[MXC-Error(tmpl-quick)] bad loop ' + cnt + ' at ' + e.shortHTMLFile); } fi = fi[0]; let m = fi.ctrl.match(inReg); if (m) { return { art: fi.art, line: fi.line, first: m[3], last: m[4], value: m[1], list: m[6], key: m[2], asc: m[5] == 1 }; } throw new Error('[MXC-Error(tmpl-quick)] bad loop ' + cnt + ' at ' + e.shortHTMLFile); }; let getIfContent = (cnt, e) => { let fi = extractArtAndCtrlFrom(cnt); if (fi.length > 1 || fi.length < 1) { throw new Error('[MXC-Error(tmpl-quick)] bad if ' + cnt + ' at ' + e.shortHTMLFile); } fi = fi[0]; let m = fi.ctrl.match(ifExtractReg); if (m) { return { art: fi.art, line: fi.line, value: m[1] }; } //console.log(m,fi); throw new Error('[MXC-Error(tmpl-quick)] bad if ' + cnt + ' at ' + e.shortHTMLFile); }; let parser = (tmpl, e) => { //console.log('parser', tmpl); let cmds = Object.create(null); tmpl = tmplCmd.store(tmpl, cmds); let current = { children: [] }; let stack = [current], textareaCount = 0; htmlParser(tmpl, { start(tag, { attrs, unary, start, attrsMap }) { let token = { tag, type: 1, ctrls: [], children: [] }; if (textareaCount) { token.start = start; } if (tag == 'textarea') { textareaCount++; } let aList = [], auto = false; for (let a of attrs) { if (a.name == quickDirectCodeAttr) { let t = tmplCmd.recover(a.value, cmds); let fi = extractArtAndCtrlFrom(t); if (fi.length > 1 || fi.length < 1) { throw new Error('[MXC-Error(tmpl-quick)] bad direct tag ' + t + ' at ' + e.shortHTMLFile); } fi = fi[0]; token.directArt = fi.art; token.directLine = fi.line; token.directCtrl = fi.ctrl; } else if (a.name == quickAutoAttr) { auto = true; } else if (a.name == quickEachAttr || a.name == quickForInAttr) { let t = tmplCmd.recover(a.value, cmds); let fi = getForContent(t, e); token.ctrls.push({ type: a.name == quickEachAttr ? 'each' : 'forin', line: fi.line, art: fi.art, first: fi.first, last: fi.last, key: fi.key, value: fi.value, list: fi.list, asc: fi.asc }); token.hasCtrls = true; } else if (a.name == quickIfAttr || a.name == quickElseIfAttr) { let t = tmplCmd.recover(a.value, cmds); //console.log(t); let fi = getIfContent(t, e); token.ctrls.push({ type: a.name == quickIfAttr ? 'if' : 'elif', line: fi.line, art: fi.art, cond: fi.value }); token.hasCtrls = true; } else if (a.name == quickElseAttr) { token.ctrls.push({ type: 'else' }); token.hasCtrls = true; } else if (a.name == quickForAttr) { let t = tmplCmd.recover(a.value, cmds); let fi = extractArtAndCtrlFrom(t); if (fi.length > 1 || fi.length < 1) { throw new Error('[MXC-Error(tmpl-quick)] bad for ' + t + ' at ' + e.shortHTMLFile); } fi = fi[0]; token.ctrls.push({ type: 'for', line: fi.line, art: fi.art, cond: fi.ctrl.replace(ifExtractReg, '$1') }); token.hasCtrls = true; } else if (a.name == tmplTempRealStaticKey) { token.canHoisting = true; token.staticValue = a.value; } else if (a.name == 'x-html' || a.name == 'inner-html') { token.xHTML = a.value; token.hasXHTML = true; } else if (a.name == tmplGroupKeyAttr) { token.groupKey = a.value; token.groupKeyNode = tag == tmplGroupTag; } else if (a.name == tmplGroupUseAttr) { token.groupUse = a.value; token.groupUseNode = tag == tmplGroupTag; } else if (a.name == 'context') { token.groupContextNode = tag == tmplGroupTag; token.groupContext = a.value; } else if (a.name != quickDeclareAttr && a.name != quickOpenAttr && !a.name.startsWith(tmplCondPrefix)) { if (a.name == 'type' && !a.unary && tag == 'input') { token.inputType = a.value; } else if (condPrefix.test(a.name)) { let cond = ''; a.name = a.name.replace(condPrefix, m => { cond = m; return ''; }); let oCond = attrsMap[`${tmplCondPrefix}${cond}`]; let extract = tmplCmd.extractCmdContent(oCond, cmds); let isRef = extract.operate == '@'; let refVar; if (isRef) { let ref = tmplCmd.extractRefContent(extract.content); refVar = ref.vars; } let refCond = e.tmplConditionAttrs[cond]; let composer = { hasExt: refCond.hasExt, condContent: extract.content, isRef, refVar, boolean: refCond.boolean, valuable: refCond.valuable, art: extract.art, line: extract.line, origin: extract.origin }; a.cond = composer; //console.log(a.name, a.value, refCond, cmds); } else if (!a.unary) { if (a.value.startsWith('\x07')) { a.value = a.value.replace(condEscapeReg, '$1?'); } else if (a.value.startsWith('\x1f')) { token.attrHasDynamicViewId = true; } else if (a.name == 'mx-view') { token.isMxView = true; } } aList.push(a); } } token.attrs = aList; token.unary = unary; token.auto = auto; //let prev = current.children[current.children.length - 1]; // we can exchange tag here // if (token.tag == 'input' && prev && prev.tag == 'span') { // current.children.pop(); // current.children.push(token, prev); // } else { current.children.push(token); //} if (!unary) { stack.push(token); current = token; } }, end(tag, { end }) { let e = stack.pop(); if (tag == 'textarea') { textareaCount--; let { children } = e; e.children = []; //e.unary = true; let value = ''; for (let c of children) { value += c.content; } e.attrs.push({ name: 'value', value, assign: '=', quote: '"' }); } if (textareaCount) { e.content = tmpl.slice(e.start, end); } if (e.hasXHTML) { e.children = [{ type: 3, isXHTML: true, content: e.xHTML }]; } current = stack[stack.length - 1]; }, chars(text) { if (text.trim()) { current.children.push({ type: 3, content: text }); } } }); return { tokens: current.children, cmds, tmpl }; }; let Directives = { 'if'(ctrl, start, end, auto) { if (configs.debug) { let open = auto ? '{{if ' : quickIfAttr + '="{{'; let art = `${open}${ctrl.art}}}${auto ? '' : '"'}`; start.push(`$__line=${ctrl.line};$__art=${JSON.stringify(art)};`); start.push(`$__ctrl=${JSON.stringify('if(' + ctrl.cond + '){')};`); } start.push(`\r\nif(${ctrl.cond}){\r\n`); end.push('\r\n}'); }, 'elif'(ctrl, start, end, auto) { start.push(`else if(`); if (configs.debug) { let open = auto ? '{{else if ' : quickElseIfAttr + '="{{'; let art = `${open}${ctrl.art}}}${auto ? '' : '"'}`; start.push(`($__line=${ctrl.line},$__art=${JSON.stringify(art)},`); start.push(`$__ctrl=${JSON.stringify('else if(' + ctrl.cond + '){')}),`); } start.push(ctrl.cond, '){\r\n'); end.push('\r\n}'); }, 'else'(ctrl, start, end) { start.push(`else{\r\n`); end.push('\r\n}'); }, 'each'(ctrl, start, end, auto) { let shortList = utils.uId('$q_a_', '', 1); let listCount = utils.uId('$q_c_', '', 1); let decs = `let ${shortList}=${ctrl.list},`; if (!longExpr.test(ctrl.list)) { decs = 'let '; shortList = ctrl.list; } let initial = ctrl.value.startsWith('$q_v_') ? '' : `let ${ctrl.value}=${shortList}[${ctrl.key}];`; if (ctrl.asc) { decs += `${listCount}=${shortList}.length`; if (ctrl.first != -1) { initial += `let ${ctrl.first}=${ctrl.key}===0;`; } if (ctrl.last != -1) { let last = utils.uId('$q_lc_', '', 1); decs += `,${last}=${listCount}-1`; initial += `let ${ctrl.last}=${ctrl.key}===${last};`; } decs += `,${ctrl.key}=0`; } else { decs += `${ctrl.key}=${shortList}.length`; if (ctrl.first != -1) { let last = utils.uId('$q_lc_', '', 1); decs += `,${last}=${ctrl.key}-1`; initial += `let ${ctrl.first}=${ctrl.key}===${last};`; } if (ctrl.last != -1) { initial += `let ${ctrl.last}=${ctrl.key}===0;`; } } if (configs.debug) { let open = auto ? '{{each ' : quickEachAttr + '="{{'; let art = `${open}${ctrl.art}}}${auto ? '' : '"'}`; start.push(`$__line=${ctrl.line};$__art=${JSON.stringify(art)};`); if (ctrl.asc) { start.push(`$__ctrl=${JSON.stringify(`for(${decs};${ctrl.key}<${listCount};${ctrl.key}++){${initial}`)};`); } else { start.push(`$__ctrl=${JSON.stringify(`for(${decs};${ctrl.key}--;){${initial}`)};`); } } //console.log(decs); if (ctrl.asc) { start.push(`\r\nfor(${decs};${ctrl.key}<${listCount};${ctrl.key}++){\r\n${initial}\r\n`); } else { start.push(`\r\nfor(${decs};${ctrl.key}--;){\r\n${initial}\r\n`); } end.push('\r\n}'); }, 'forin'(ctrl, start, end, auto) { let initial = ctrl.value.startsWith('$q_v_') ? '' : `let ${ctrl.value}=${ctrl.list}[${ctrl.key}];`; if (configs.debug) { let open = auto ? '{{forin ' : quickForInAttr + '="{{' let art = `${open}${ctrl.art}}}${auto ? '' : '"'}`; start.push(`$__line=${ctrl.line};$__art=${JSON.stringify(art)};`); start.push(`$__ctrl=${JSON.stringify(`for(let ${ctrl.key} in ${ctrl.list}){${initial}`)};`); } start.push(`\r\nfor(let ${ctrl.key} in ${ctrl.list}){\r\n${initial}\r\n`); end.push('\r\n}'); }, 'for'(ctrl, start, end, auto) { if (configs.debug) { let open = auto ? '{{for ' : quickForAttr + '="{{' let art = `${open}${ctrl.art}}}${auto ? '' : '"'}`; start.push(`$__line=${ctrl.line};$__art=${JSON.stringify(art)};`); start.push(`$__ctrl=${JSON.stringify(`for(${ctrl.cond}){`)};`); } start.push(`\r\nfor(${ctrl.cond}){\r\n`); end.push('\r\n}'); } }; let preProcess = (src, e) => { //console.log('enter',JSON.stringify(src)); let cmds = Object.create(null), tags = Object.create(null); src = src.replace(directReg, m => { return `<${quickDirectTagName} ${quickDirectCodeAttr}="${m.replace(/"/g, '&quot;')}"/>`; }).replace(spreadAttrsReg, m => { return `${quickSpreadAttr}="${m.replace(/"/g, '&quot;')}"`; }); src = artExpr.addLine(src); src = tmplCmd.store(src, cmds); src = tmplCmd.store(src, cmds, artCommandReg); let count = 0; //以上处理模板命令,然后是合法的html标签 /* 我们要区别对待 1. <div> a {{if cond}} b {{/if}} c </div> 2. <div> {{if cond}} <div>cond</div> {{/if}} </div> 在文本中的命令语句与在标签中的命令语句处理不同,所以要先把最内部的处理下 */ src = storeInnerMatchedTags(src, tags); src = storeHTML(src, tags); src = src.replace(tmplCommandAnchorReg, m => { let ref = cmds[m]; if (ref) { let i = artExpr.extractArtInfo(ref); if (i) { let { art, ctrls, line } = i; let sourceArt = ` ${quickSourceArt}="${attrMap.escapeAttr(art)}"`; if (ctrls[0] == 'each') { return `<${quickGroupTagName}${sourceArt} ${quickAutoAttr} ${quickOpenAttr}="<%{%>" ${quickEachAttr}="{{\x1e${line}${art.substring(5)}}}">`; } else if (ctrls[0] == 'forin') { return `<${quickGroupTagName}${sourceArt} ${quickAutoAttr} ${quickOpenAttr}="<%{%>" ${quickForInAttr}="{{\x1e${line}${art.substring(6)}}}">`; } else if (ctrls[0] == 'for') { return `<${quickGroupTagName}${sourceArt} ${quickAutoAttr} ${quickOpenAttr}="<%{%>" ${quickForAttr}="{{\x1e${line}${art.substring(4)}}}">`; } else if (ctrls[0] == 'if') { return `<${quickGroupTagName}${sourceArt} ${quickAutoAttr} ${quickOpenAttr}="<%{%>" ${quickIfAttr}="{{\x1e${line}${art.substring(3)}}}">`; } else if (ctrls[0] == 'else') { if (ctrls[1] == 'if') { return `</${quickGroupTagName} ${quickCloseAttr}="<%}%>"><${quickGroupTagName}${sourceArt} ${quickAutoAttr} ${quickOpenAttr}="<%{%>" ${quickElseIfAttr}="{{\x1e${line}${art.substring(7)}}}">`; } return `</${quickGroupTagName} ${quickCloseAttr}="<%}%>"><${quickGroupTagName}${sourceArt} ${quickAutoAttr} ${quickOpenAttr}="<%{%>" ${quickElseAttr}>`; } else if (art.startsWith('/each') || art.startsWith('/forin') || art.startsWith('/for') || art.startsWith('/if')) { return `</${quickGroupTagName} ${quickCloseAttr}="<%}%>">`; } } else { return m; } } return m; }); src = tmplCmd.store(src, cmds, artCommandReg); //console.log(src); src = storeHTML(src, tags); //console.log(src); while (tagHReg.test(src)) { tagHReg.lastIndex = 0; src = src.replace(tagHReg, m => { m = tags[m]; let src = m.src; //console.log('src',src,m.tag); if (m.tag) { src = src.replace(quickLoopReg, (_, k, $, c) => { c = tmplCmd.recover(c, cmds); let li = artExpr.extractArtInfo(c); if (li) { let expr = artExpr.extractAsExpr(li.art); //console.log(expr,li.art); if (!expr.value) { expr.value = utils.uId('$q_v_', '', 1); } if (expr.bad || expr.splitter != 'as') { slog.ever(chalk.red(`[MXC-Error(tmpl-quick)] unsupport or bad ${k} {{${li.art}}} at line:${li.line}`), 'file', chalk.grey(e.shortHTMLFile)); throw new Error(`[MXC-Error(tmpl-quick)] unsupport or bad ${k} {{${li.art}}} at ${e.shortHTMLFile}`); } if (!expr.index) { expr.index = utils.uId('$q_key_', '', 1); } let firstAndLastVars = ''; let flv = ''; if (expr.first) { firstAndLastVars += ',' + expr.first; flv += ',' + expr.first; } else { firstAndLastVars += ',-1'; } if (expr.last) { firstAndLastVars += ',' + expr.last; flv += ',' + expr.last; } else { firstAndLastVars += ',-1'; } let prefix = ''; if (!m.special) { count++; prefix = quickOpenAttr + '="<%{%>" '; } //console.log(expr.value); return `${prefix}${quickDeclareAttr}="<%let ${expr.index},${expr.value}=${expr.iterator}[${expr.index}]${flv}%>" ${k}="<%'${li.line}\x11${li.art.replace(escapeSlashRegExp, '\\$&')}\x11'%><%(${expr.value},${expr.index}${firstAndLastVars},${expr.asc ? 1 : -1}) in ${expr.iterator}%>"`; } return _; }).replace(quickConditionReg, (_, k, $, c) => { c = tmplCmd.recover(c, cmds); //console.log('qod',c); let li = artExpr.extractArtInfo(c); if (li) { let expr = artExpr.extractIfExpr(li.art); let key = k == quickForAttr ? 'for' : 'if'; return `${k}="<%'${li.line}\x11${li.art.replace(escapeSlashRegExp, '\\$&')}\x11'%><%${key}(${expr});%>"`; } return _; }); } return src; }); } if (count) { src = src.replace(lastCloseReg, (m, more) => { return ` ${quickCloseAttr}="<%${new Array(count + 1).join('}')}%>">${more}`; }); } for (let c in cmds) { let v = cmds[c]; if (util.isString(v)) { v = artExpr.removeLine(v); cmds[c] = v; } } src = tmplCmd.recover(src, cmds); //console.log('here',JSON.stringify(src)); src = artExpr.recoverEvent(src); //console.log(src); return src; }; let combineSamePush = (src, pushed) => { let start = -1, prev = '', ranges = [], lastChar = ''; for (let p of pushed) { let i = src.indexOf(p.src, start); if (i >= 0) { if (i == start && prev == p.key) { if (!lastChar) { lastChar = src.charAt(i - 2); } ranges.push({ char: ',', start: i - 2,//$vnode_.push($_create()); trim ); srcEnd: i + p.src.length, end: i + p.key.length + 6 //$vnode_.push($_create()); trim $vnode_.push( }); } else { if (lastChar) { let last = ranges[ranges.length - 1]; ranges.push({ char: lastChar, start: last.srcEnd - 2, end: last.srcEnd - 1 }); lastChar = ''; } } start = i + p.src.length; prev = p.key; } } if (lastChar) { let last = ranges[ranges.length - 1]; ranges.push({ char: lastChar, start: last.srcEnd - 2, end: last.srcEnd - 1 }); } for (let i = ranges.length; i--;) { let r = ranges[i]; src = src.substring(0, r.start) + r.char + src.substring(r.end); } return src; }; let process = (src, e) => { let { cmds, tokens } = parser(`${src}`, e); let snippets = []; let vnodeDeclares = Object.create(null), vnodeInited = Object.create(null), combinePushed = [], staticVars = [], specialStaticVars = {}, specialFlags = {}, specialFlagIndex = 0, groupKeyAsParams = Object.create(null), staticNodes = Object.create(null), staticObjects = Object.create(null), staticCounter = 0, staticUniqueKey = md5(e.shortHTMLFile, tmplStaticVarsKey), rootCanHoisting = true; let genElement = (node, level, inStaticNode) => { if (node.type == 3) { let cnt = tmplCmd.recover(node.content, cmds); let text = serAttrs('$text', cnt, false); vnodeDeclares.$text = 1; if (text.hasCmdOut || text.hasCharSnippet) { let outText = '', safeguard = false; if (text.direct) { outText = text.returned; } else { snippets.push(text.returned + ';'); outText = '$text'; safeguard = !text.hasSnippet; } let xHTML = node.isXHTML ? '1' : '0'; if (vnodeInited[level]) { if (!safeguard) { combinePushed.push({ key: `$vnode_${level}`, src: `$vnode_${level}.push($_create(0,${xHTML},${outText}));` }); } snippets.push(`$vnode_${level}.push($_create(0,${xHTML},${outText}));`); } else { vnodeInited[level] = 1; snippets.push(`$vnode_${level}=[$_create(0,${xHTML},${outText})];`); } } else { snippets.push(text.returned + ';'); } } else { let attrs = {}, attrsStr = '', ctrlAttrs = [], hasInlineCtrl = false, hasAttrs = false, hasCmdOut = node.attrHasDynamicViewId, dynamicAttrs = '', hasCtrl, hasRestElement = false, attrKeys = Object.create(null), specialKey = ''; if (node.attrs.length) { hasAttrs = true; for (let a of node.attrs) { if (node.isMxView && a.name == tmplMxViewParamKey && groupKeyAsParams.groups) { a.value = a.value.replace(groupsReg, '#'); } if (configs.tmplRadioOrCheckboxRename && a.name == 'name' && a.value && (node.inputType == 'radio' || node.inputType == 'checkbox')) { let newValue = ''; tmplCommandAnchorReg.lastIndex = 0; if (tmplCommandAnchorReg.test(a.value)) { tmplCommandAnchorReg.lastIndex = 0; if (configs.projectName) { newValue = `${configs.projectName}_${a.value}`; } else { newValue = a.value; } } else { newValue = (configs.projectName ? configs.projectName + '_' : '') + md5(e.from + ':' + a.value, tmplRadioOrCheckboxKey, '', true); } a.value = newValue; } if (a.unary) { a.value = true; } else { a.value = tmplCmd.recover(a.value, cmds); } if (attrKeys[a.name] === 1 && e.checker.tmplDuplicateAttr) { let v = a.unary ? '' : `="${a.value}"`; slog.ever(chalk.red('[MXC Tip(tmpl-quick)] duplicate attr:' + a.name), 'near:', chalk.magenta(a.name + v), ' at file:', chalk.grey(e.shortHTMLFile)); continue; } attrKeys[a.name] = 1; let bProps = attrMap.getBooleanProps(node.tag, node.inputType); let bAttr = bProps[a.name]; if (a.name == a.value || !a.value) { if (bAttr) { a.value = true; } } let oKey = a.name.replace(escapeSlashRegExp, '\\$&'); let key = `$$_${a.name.replace(/[^a-zA-Z]/g, '_')}`; //console.log('leave', a.value); let attr = serAttrs(key, a.value, !bAttr); //console.log(attr,a); hasCtrl = attr.hasCtrl; if (attr.hasCmdOut || attr.hasVarOut || a.cond) { hasCmdOut = true; } if (a.name == quickSpreadAttr) { attr.direct = false; } let cond = ''; let outputBoolean = false; if (a.cond) { let { line, art, hasExt, condContent, origin, valuable, isRef, refVar } = a.cond; outputBoolean = !valuable && !hasExt; //<input disabled="{{=user.checked}}?"/> if (a.value === true || outputBoolean) { attr.returned = ''; cond += '('; } else if (attr.direct) { //<input value="{{=user}}?{{=user.value}}"/> //<input value="{{=user.age}}?"/> let v = hasExt ? '' : tmplVarTempKey + '='; cond += `(${v}(`; attr.returned = `(${attr.returned})`; } else { //console.log('xxxx'); cond += `((`; } //console.log(attr, cond, a); if (configs.debug) { cond += `$__line=${line},$__art='{{${art}}}',$__ctrl='<%${origin}%>',`; } cond += isRef ? refVar : condContent; if (a.value === true || outputBoolean) { cond += ')'; } else { if (valuable) { cond += '))!=null&&'; } else { cond += '))&&'; } if (!hasExt) { attr.returned = tmplVarTempKey; } } } //console.log(a.cond); //console.log(cond, attr.returned,a.value,outputBoolean); if (configs.debug && attr.direct && (bAttr || (a.cond && !a.cond.hasExt && !a.cond.valuable)) && (a.value !== true || outputBoolean || a.cond)) { if (a.value === true || outputBoolean) { cond = `(${tmplVarTempKey}=${cond},${tmplVarTempKey}!==true&&${tmplVarTempKey}!==false&&console.error('make sure attr:"${a.name}" returned only true or false value\\r\\nat line:'+$__line+'\\r\\nat file:${e.shortHTMLFile}\\r\\ncurrent returned value is:',JSON.stringify(${tmplVarTempKey})),${tmplVarTempKey})`; } else if (attr.direct) { let assign = attr.returned == tmplVarTempKey ? '' : `${tmplVarTempKey}=${attr.returned},`; attr.returned = `(${assign}${tmplVarTempKey}!==true&&${tmplVarTempKey}!==false&&console.error('make sure attr:"${a.name}" returned only true or false value\\r\\nat line:'+$__line+'\\r\\nat file:${e.shortHTMLFile}\\r\\ncurrent returned value is:',JSON.stringify(${tmplVarTempKey})),${tmplVarTempKey})`; } } //console.log(attr.returned); if (attr.direct) { if (hasRestElement) { ctrlAttrs.push({ ctrl: cond + attr.returned, type: 'direct', oKey }); } else { attrs[oKey] = cond + attr.returned; } } else { hasInlineCtrl = true; if (a.name == quickSpreadAttr) { hasRestElement = true; ctrlAttrs.push({ type: 'mixed', ctrl: attr.returned }); } else { vnodeDeclares[key] = 1; ctrlAttrs.push({ ctrl: attr.returned, oKey, key: cond + key }); } } if (configs.debug && bAttr && !attr.direct) { ctrlAttrs.push({ ctrl: `(${key}!==true&&${key}!==false&&console.error('make sure attr:"${a.name}" returned only true or false value\\r\\nat line: '+$__line+'\\r\\nat file:${e.shortHTMLFile}\\r\\ncurrent returned value is:',JSON.stringify(${key})));` }); } } let allProps = attrMap.getProps(node.tag, node.inputType); let mustUseProps = []; if (hasInlineCtrl) { if (hasRestElement) { for (let c of ctrlAttrs) { if (c.type != 'mixed' && c.type != 'direct') { dynamicAttrs += c.ctrl; } } attrsStr = '{'; for (let p in attrs) { attrsStr += `'${p}': ${attrs[p]},`; if (allProps[p]) { mustUseProps.push(`'${p}':'${allProps[p]}'`); } } for (let c of ctrlAttrs) { if (c.type == 'direct') { attrsStr += `'${c.oKey}': ${c.ctrl},`; if (allProps[c.oKey]) { mustUseProps.push(`'${c.oKey}':'${allProps[c.oKey]}'`); } } else if (c.type == 'mixed') { attrsStr += `...${c.ctrl}, `; } else if (c.oKey) { attrsStr += `'${c.oKey}': ${c.key},`; if (allProps[c.oKey]) { mustUseProps.push(`'${c.oKey}':'${allProps[c.oKey]}'`); } } } attrsStr += '}'; } else { dynamicAttrs += ';'; for (let c of ctrlAttrs) { dynamicAttrs += c.ctrl; } attrsStr = '{'; for (let p in attrs) { attrsStr += `'${p}': ${attrs[p]},`; if (allProps[p]) { mustUseProps.push(`'${p}':'${allProps[p]}'`); } } for (let c of ctrlAttrs) { if (c.oKey) { attrsStr += `'${c.oKey}': ${c.key},`; if (allProps[c.oKey]) { mustUseProps.push(`'${c.oKey}':'${allProps[c.oKey]}'`); } } } attrsStr += '}'; } } else { attrsStr = '{'; for (let p in attrs) { attrsStr += `'${p}': ${attrs[p]},`; if (allProps[p]) { mustUseProps.push(`'${p}':'${allProps[p]}'`); } } attrsStr += '}'; if (!hasCmdOut && !hasCtrl && !node.canHoisting && node.tag != quickGroupTagName && node.tag != quickDirectTagName && !node.groupKeyNode && !node.groupUseNode) { let i = staticObjects[attrsStr]; if (i) { attrsStr = i.key; i.used++; if (!inStaticNode) { i.inStatic = false; } } else { let key = `$quick_${staticUniqueKey}_${staticCounter++}_static_attr`; staticObjects[attrsStr] = { key, used: 1, inStatic: inStaticNode }; attrsStr = key; } } } if (mustUseProps.length) { let specials = '{', flag = 0; for (let p of mustUseProps) { specials += `${p},`; if (!specialFlags[p]) { specialFlags[p] = 2 << specialFlagIndex; specialFlagIndex++; } flag |= specialFlags[p]; } specials += '}'; specialKey = `$special_${flag}`; if (!specialStaticVars[specialKey]) { specialStaticVars[specialKey] = specials; } } } let ctrls = node.ctrls; let start = [], end = []; if (ctrls.length) { for (let ctrl of ctrls) { let fn = Directives[ctrl.type]; if (fn) { fn(ctrl, start, end, node.auto); } } } snippets.push(`${start.join('')}`); let key = ''; if (node.groupKeyNode) { if (node.groupContextNode) { snippets.push(`\ngroups.${node.groupKey}=${node.groupContext}=>{\n`); groupKeyAsParams.groups = 1; groupKeyAsParams[node.groupContext] = 1; } if (node.canHoisting) { key = staticNodes[node.staticValue]; if (!key) { key = `$quick_group_${staticUniqueKey}_${node.groupKey}_static_node`; staticVars.push({ key }); staticNodes[node.staticValue] = key; } } else { key = `$$_group_` + node.groupKey; vnodeDeclares[key] = 1; } } if (node.canHoisting) { //console.log(node); if (node.groupKeyNode) { snippets.push(`\r\nif(!${key}){\r\n`); } else { key = staticNodes[node.staticValue]; //let exists = key; if (!key) { key = `$quick_${staticUniqueKey}_${staticCounter++}_static_node`; staticVars.push({ key }); staticNodes[node.staticValue] = key; } snippets.push(`\r\nif(${key}){\r\n`); if (vnodeInited[level]) { snippets.push(`$vnode_${level}.push(${key});`); } else { snippets.push(`$vnode_${level}=[${key}];`); } snippets.push(`\r\n}else{\r\n`); } } if (node.children.length) { vnodeDeclares['$vnode_' + (level + 1)] = 1; delete vnodeInited[level + 1]; for (let e of node.children) { if (e.hasCtrls) { snippets.push(`$vnode_${level + 1}=[];`); vnodeInited[level + 1] = 1; break; } } for (let e of node.children) { genElement(e, level + 1, inStaticNode || node.canHoisting); } } if (node.tag == quickGroupTagName) { if (node.children.length) {