lite
Version:
A cross platform template engine base on xml/html and javascript expression.
442 lines (419 loc) • 11.6 kB
JavaScript
/*
* List Template
* License LGPL(您可以在任何地方免费使用,但请不要吝啬您对框架本身的改进)
* http://www.xidea.org/project/lite/
* @author jindw
* @version $Id: template.js,v 1.4 2008/02/28 14:39:06 jindw Exp $
*/
var findELEnd=require('./el-util').findELEnd;
var parseChildRemoveAttr=require('./syntax-util').parseChildRemoveAttr;
var compressJS=require('./js-token').compressJS;
var findXMLAttribute=require('./xml').findXMLAttribute;
var XML_SPACE_TRIM=require('./parse-xml').XML_SPACE_TRIM;
/**
* false:preserved
* true:trim all
* null:default trim muti-space to only one,(and trim first,last space)?
* none:
*/
var AUTO_FORM_PREFIX = "http://www.xidea.org/lite/attribute/h:autofrom"
var AUTO_FORM_SELETED = "http://www.xidea.org/lite/attribute/h:autofrom#selected"
var HAS_LAZY_WIDGET = "http://www.xidea.org/lite/core/c:widget#has-lazy";
var WRAP_SCRIPT_USE = "http://www.xidea.org/lite/attribute/h:wrap_script_use"
var HTML = {
xmlns : function(){},
/* 处理IE条件注视 */
parse$8:parseConditionComment,
/* 处理自动表单填充 */
parseSelect:parseSelect,
parseOption:parseOption,
parseInput:parseInput,
parseTextArea:parseTextArea,
/* 处理 script中的模板变量(JSON.stringify)*/
parseScript : parseHtmlScript,
parseHead:parseHtmlHead,
parseHtml:parseHtml,
parseLink:parseLink,
/* 处理保留空白的html节点 */
parsePre : preservedParse,
//parseTextArea = preservedParse;//processed on auto form
/* 处理 html 事件中的模板变量(JSON.stringify) (ELEMENT_NODE == 2)*/
"onon*":parseHtmlEventAttr,
onclass:parseHtmlClassAttr,
/* 处理html 资源地址属性中的模板变量(encodeURI) */
//if(tagName=='link'){
//}else if(/^a/i.test(tagName)){
onhref:autoURIEncoder,
// if(/^form$/i.test(tagName)){
onaction:autoURIEncoder,
//if(/^(?:script|img|button)$/i.test(tagName)){
//}else if(/^(?:a|frame|iframe)$/i.test(tagName)){
onsrc:autoURIEncoder,
}
var HTML_EXT = {
xmlns : function(){},
interceptAutoform:interceptAutoform,
interceptTrim:interceptTrim,
parseAutoform:interceptAutoform,
parseTrim:interceptTrim,
}
exports.HTML=HTML;
exports.HTML_EXT=HTML_EXT;
exports.wrapScript = wrapScript;
/* html form auto value*/
function parseHtmlHead(head){
this.setAttribute(parseHtmlHead,0);
var doc = head.ownerDocument;
this.next(head);
this.setAttribute(parseHtmlHead,this.mark());
}
function __widget_arrived(id,data){
if(data instanceof Function){
if(/^lazy/.test(id)){
__widget_arrived[id]=data;
}else{
data();
}
}else{
document.querySelector('[data-widget='+id+']').innerHTML=data;
if(data=__widget_arrived[id]){
delete __widget_arrived[id];
data();
}
}
}
function parseHtml(html){
var links = [];
this.setAttribute(parseHtml,links)
this.next(html);
var headMark = this.getAttribute(parseHtmlHead);
if(headMark){
var hasLazyWidget = this.getAttribute(HAS_LAZY_WIDGET);
var result = this.reset(headMark);
for(var i = 0;i<links.length;i++){
this.next(links[i]);
}
if(hasLazyWidget){
var createScript = "<script>var __widget_arrived=__widget_arrived||"+String(__widget_arrived).replace(/[\r\n\t]|\s*(=)\s*/g,'$1')+'</script>'
this.appendText(createScript)
}
this.appendAll(result);
}
//console.log('#$$$$'+this.getAttribute(parseHtml))
}
function parseLink(link){
var list = this.getAttribute(parseHtml)
var headMark = this.getAttribute(parseHtmlHead);
if(list && headMark > 0){
list.push(link)
}else{
this.next(link);
}
//console.log(list,'#'+this.getAttribute(parseHtmlHead))
}
function parseInput(el){
var autoform = this.getAttribute(AUTO_FORM_PREFIX);
if(autoform!=null){
var name_ = el.getAttribute('name');
//console.warn(uneval(autoform),name_);
if(name_){
var type = el.getAttribute('type');
if(!/^(?:reset|button|submit)$/i.test(type)){
if(/^(?:checkbox|radio)$/i.test(type)){
if(!el.hasAttribute('checked')){
buildCheck2select(this,el,name_,'checked',/checkbox/i.test(type));
return ;
}
}else if(!el.hasAttribute('value')){
el.setAttribute('value', "${"+name_+"}");
}
}
}
}
this.next(el);
}
function parseTextArea(el){
var oldSpace = this.getAttribute(XML_SPACE_TRIM);
this.setAttribute(XML_SPACE_TRIM,false);
var autoform = this.getAttribute(AUTO_FORM_PREFIX);
var hasValue = el.hasAttribute('value');//value added for textarea
if(hasValue){
el.textContent = el.getAttribute('value');
}else if(autoform!=null && !el.hasChildNodes()){
var name_ = el.getAttribute('name');
el.textContent = "${"+ name_ + "}";
}
this.next(el);
this.setAttribute(XML_SPACE_TRIM,oldSpace);
}
function parseSelect(el){
var multiple = el.hasAttribute('multiple');
this.setAttribute(AUTO_FORM_SELETED,[el.getAttribute('name'),multiple]);//不清理也无妨
this.next(el);
}
function parseOption(el){
var autoform = this.getAttribute(AUTO_FORM_PREFIX);
if(autoform!=null){
var name_multiple = this.getAttribute(AUTO_FORM_SELETED);
if(name_multiple){
buildCheck2select(this,el,name_multiple[0],'selected',name_multiple[1]);
return;
}
}
this.next(el);
}
function interceptAutoform(node){
var oldAutoform = this.getAttribute(AUTO_FORM_PREFIX);
try{
var prefix = findXMLAttribute(node,'*value');
//console.info("#####",prefix);
if(prefix == 'true'){
prefix = '';
}
this.setAttribute(AUTO_FORM_PREFIX,prefix);
parseChildRemoveAttr(this,node);
}finally{
this.setAttribute(AUTO_FORM_PREFIX,oldAutoform);
}
}
/**
* <!--[if IE]><p>11</p><![endif]-->
*
* <!--[if IE 8]><!-->
* <p>aa</p>
* <!--<![endif]-->
*/
function parseConditionComment(comm){//comment
var text = comm.textContent || comm.data;
var match = text.match(/^\[if\s[^\]]+\]>|<!\[endif\]$/ig);
if(match){
if(match.length == 1){
this.appendText('<!--'+text+'-->')
}else{
var len1 = match[0].length;
var len2 = match[1].length
var content = text.substring(len1,text.length - len2);
try{
if(/^\s*</.test(content)){
content = this.loadXML(content);
}
//xml = this.xml.documentElement;
}catch(e){
}
this.appendText('<!--'+match[0]);
this.parse(content);
this.appendText(match[1]+'-->');
}
}
}
/**
* safe
* any
* none
*/
function interceptTrim(node){
var oldSpace = this.getAttribute(XML_SPACE_TRIM);
try{
var value = findXMLAttribute(node,'*value');
this.setAttribute(XML_SPACE_TRIM,value == 'true'?true:value == 'false'?false:null);
// console.error(this.getClass(),XML_SPACE_TRIM,this.getAttribute(XML_SPACE_TRIM));
parseChildRemoveAttr(this,node);
}finally{
this.setAttribute(XML_SPACE_TRIM,oldSpace);
}
}
function toelv(value){
if(value){
var elv = value.replace(/^\$\{([\s\S]+)\}$/,'$1');
try{
if(elv != value){
new Function("return "+elv);
}
}catch(e){
elv = value;
}
if(elv == value){
elv = JSON.stringify(value);
}
}
return elv;
}
function buildCheck2select(context,el,name_,checkName,multiple){
var value = el.getAttribute('value');
if(!value && checkName == 'selected'){
value = el.textContent;
}
var elv = toelv(value);
if(!elv){
context.next(el);
return;
}
var forId = context.allocateId();
var flag = context.allocateId();
if(multiple){
context.appendVar(flag,'true');
context.appendFor(forId,"[].concat("+name_+')',null);
context.appendIf(flag +'&&'+ forId+'+""===""+'+elv);
el.setAttribute(checkName,checkName);
context.appendVar(flag,'false');
context.next(el)
context.appendEnd();
context.appendEnd();
context.appendIf(flag);
el.removeAttribute(checkName);
context.next(el)
context.appendEnd();
}else{
context.appendIf(name_+'+""===""+'+elv);
el.setAttribute(checkName,checkName);
context.next(el);
context.appendEnd();
context.appendElse(null);
el.removeAttribute(checkName);
context.next(el);
context.appendEnd();
}
}
/* html parse */
function preservedParse(node){
var oldSpace = this.getAttribute(XML_SPACE_TRIM);
this.setAttribute(XML_SPACE_TRIM,false);
try{
this.next(node);
}finally{
this.setAttribute(XML_SPACE_TRIM,oldSpace);
}
}
function parseHtmlScript(el){
var oldSpace = this.getAttribute(XML_SPACE_TRIM);
this.setAttribute(XML_SPACE_TRIM,false);
try{
if(!el.hasAttribute('src')){
var doc = el.ownerDocument;
var wrap_script = this.getAttribute(WRAP_SCRIPT_USE);
var child;
var buf = [];
while(child = el.firstChild){
if(child.nodeType==3 || child.nodeType == 4){//text/cdata
buf.push(child.data);
}else{
console.warn('script 中不能用嵌入html标签,建议将脚本放在 <![CDATA[...]]>中。');
}
el.removeChild(child);
}
var source = processJS(this,buf.join(''));
if(wrap_script){
wrapScript(source,wrap_script)
}
if(source.search(/[<&]/)>=0){
el.appendChild(doc.createTextNode('/*'));
el.appendChild(doc.createCDATASection('*/'+source+'//'));
}else{
el.appendChild(doc.createTextNode(source));
}
}
this.next(el);
}finally{
this.setAttribute(XML_SPACE_TRIM,oldSpace);
}
}
function wrapScript(source,wrap_script_method){
var callMatch = source.match(/^\s*([\w\.]+)\(([\s\S]*)\)\s*;?\s*$/);
var callMethod = callMatch && callMatch[1];
if(!/^(?:__widget_arrived|(?:\w+\.)?use)$/.test(callMethod)){
var rexp = /\brequire\((['"][\.\-\w@\/]+["'])\)|^\s*\/(?:\/.*|\*[\s\S]*?\*\/)|'(?:[^'\\\r\n]|\\.)*'|"(?:[^"\\\r\n]|\\.)*"/g;
var deps = [];
var m;
while(m = rexp.exec(source)){
m[1] && deps.push(m[1]);
}
//if(deps.length){
source = wrap_script_method + '(['+deps.join(',')+'],function(){'+source+'\n})'
//}
}
return source;
}
function parseHtmlClassAttr(attr){
attr.value = attr.value.replace(/\s*?[\r\n]\s*/g,' ');
this.next(attr)
}
function parseHtmlEventAttr(attr){
var value = attr.value.replace(/\s*?([\r\n])\s*/g,'$1');
attr.value = processJS(this,value);
this.next(attr);
}
function processJS(ctx,value){
var value2 = value.replace(/^\s*\$\{([\s\S]+)\}\s*$/,'return $1');//当行属性脚本
if(value2 != value){
try{
new Function(value2);//属性中直接插入的脚本(没有语句,不可能是json变量)
//console.error(value2)
return value;//直接当普通脚本输出
}catch(e){
}
}
value = compressJS(value);
return autoEncode(value,/^\s*JSON\s*\.\s*stringify\s*\(/,replaceJSON);
}
function replaceJSON(v){
return "JSON.stringify("+v+")";
}
function replaceURI(v){
return "encodeURI("+v+")";
}
function autoURIEncoder(attr){
var value = attr.value;
attr.value = autoEncode(value,/^\s*encodeURI\s*\(/,replaceURI,encodeURI);
this.next(attr);
}
/**
* TODO: 自动编码压缩再解码
*/
function autoEncode(value,pattern,elReplacer,staticReplacer){
var p = -1;
var result = [];
while(true){
p = value.indexOf("${",++p);
if(p>=0){
if(!(countEescape(value,p) % 2)){
var p2 = findELEnd(value,p+1);
if(p2>0){
var el = value.substring(p+2,p2);
if(!pattern.test(el)){
el = elReplacer(el);
}
var prefix = value.substring(0,p);
if(staticReplacer){
prefix = staticReplacer(prefix);
}
result.push(prefix,'${',el,'}');
value = value.substring(p2+1)
p=-1;
}else{
p++;
}
}
}else{
break;
}
}
if(staticReplacer){
value = staticReplacer(value);
}
if(result.length){
result.push(value);
return result.join('');
}else{
return value;
}
}
function countEescape(text, p$) {
if (p$ > 0 && text.charAt(p$ - 1) == '\\') {
var pre = p$ - 1;
while (pre-- > 0 && text.charAt(pre) == '\\')
;
return p$ - pre - 1;
}
return 0;
}
//autoEncode("${a}",/^encodeURI*/,function(a){return 'encodeURI('+a+')'})