﻿/*
DAPMX core 0.2.1
Copyright 2010 Maxim Sajin <joox@dapmx.org>
Regarding licensing information please refer http://dapmx.org
*/

/// dap launch

if(!window.onload)window.onload=function(){
	var remap="page@g article@a critn@c count@n item@i primary@p class@p kit@p related@r brand@b bra@b type@t dept@e dept@l";
	daNormalizeUrl(remap)||dapDocument(document.body,daQueryString());
};

function dapDocument(root,data){// activate dap root node
	
	var	place	=root.parentNode,
		rootxml	=root,//isIE?daParseXML(root.outerHTML):root, // in IE, markup is re-parsed to get rid of fancy property-attributes
		loading	=document.getElementById("loading")||newElemText(root.tagName,"loading..."),
		rootns	=null;
		
	place.replaceChild(loading,root);
	
	// process inline namespaces
	for(var docns=rootxml.getElementsByTagName("namespace"),i=docns.length,ns;i-->0;)
		if(ns=docns[i].parentNode.removeChild(docns[i]))
			rootns=daNamespace(ns,ns.getAttribute("dapns")||window.location.href);
	if(rootns)dapnsStack.unshift(rootns);
	
	// dap the root node
	daImportNS(dapnsStack[0],rootxml.getAttribute("dapns-import"));
	place.replaceChild(daTemplate(rootxml).spawn(place,[{'':data}]),loading);
	postprocess.run();
};


var	_DAP_ERRORS=[],			// dap errors are logged into this array
	daDEFAULT_ELEMENT="div",	// default element is enoted in dap markup as <o> element
	daDEFAULT_EVENT="click",
	daRESERVED={			// dap-reserved attribute names. please, don't misuse them
		"dapns":true,		// namespace URI
		"dapns-import":true,	// imported namespaces
		"dapmodel":true		// element model override
	};
	
function daWarn(reason,subj){		// small errors will log into _DAP_ERRORS
	_DAP_ERRORS.push({subj:subj,reason:reason,context:daContexts[0]});daTitle("DAP ERRORS: "+_DAP_ERRORS.length);
};
function daFail(reason,subj){		// severe errors will terminate execution
	daWarn(reason,subj);
	throw new Error("dap error <"+subj+" :: "+reason+">");
};


var dap={

	convert	:{
		""	:function(value){return String(value)},
		copy	:function(value){return daCopy(value)},
		
		esc	:function(value){return escape(value)},		/// escape
		usc	:function(value){return unescape(value)},	/// unescape
		
		// base64 encoding is borrowed from external js
		// <script src="http://www.webtoolkit.info/djs/webtoolkit.base64.js" type="text/javascript"></script>
		encode64:function(value){return value?daEncode64(value):""},
		decode64:function(value){return value?daDecode64(value):""},
		
		absurl	:function(value){if(value)return daAbsUrl(value)},
		fromurl	:function(value){return daQueryString()},
		
		xmlenc	:function(value){if(value)return value.replace(/\&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gr;")},
		fname	:function(value){if(value)return value.split("/").pop()},
		ext	:function(value){if(value)return value.split(".").pop()},	/// file extension
		num	:function(value){return String(value).replace(/\B(?=(...)*$)/g," ")},		/// digit groups
		"~num"	:function(value){return value.replace(/\D/g,"")||0},
				
		"?"	:function(bool){return bool?true:false},	/// test
		"!"	:function(bool){return bool?false:true},	/// test inverse
		"#"	:function(bool){return bool?"+":"-"},		/// test as +/-
		
		"+?"	:function(num){return parseInt(num)>0},	/// test positive
		"-?"	:function(num){return parseInt(num)<0},	/// test negative
		"0?"	:function(num){return parseInt(num)==0},	/// test zero
		
		"+"	:function(num){return (num=parseFloat(num))?String(Math.abs(num)):""},	/// abs
		"-"	:function(num){return (num=parseFloat(num))?String(-num):""},		/// neg
		"~"	:function(num){return (num=parseFloat(num))?(num>0?"+":"-"):"0"},	/// sgn
		
		space	:function(value){return daSplit(daTrim(value)," ")},
		lf	:function(value){return daSplit(value,"\n")},				///(line feed)
		csv	:function(value){return daSplit(value,",")},				/// x1,x2,x3 (comma-separated values)
		bar	:function(value){return daSplit(value,"|")},				/// x1|x2|x3 (bar-separated values)
		nvp	:function(value){return daSplit(daSplit(value,";"),":")},		/// x1:y1:z1;xN:yN:zN (named after "name:value"-pairs)
		lfc	:function(cdata){return daSplit(daSplit(daTrim(cdata),/\n\s*/),/\s*:/)},/// linefeed-colon

		xml2dom	:function(value){return daParseXML(value)},
		dom2xml	:function(value){return daSerialize(value)},
		obj2dom	:function(value){return daDumbDOM(value,"object")},
		obj2xml	:function(value){return daSerialize(daDumbDOM(value,"object"))},
		prettyprint:function(value){return daPrettyPrint(value)},
		
		dict	:function(value){return daModel(value,dap.model.dict)},
		flow	:function(value){return daModel(value,dap.model.flow)},
		rset	:function(value){return daModel(value,dap.model.rset)},
		book	:function(value){return daModel(value,dap.model.book)},
		"dict*"	:function(value){return daModel(value,dap.model["dict*"])},
		"flow*"	:function(value){return daModel(value,dap.model["flow*"])},
		dapns	:function(value){return daModel(value,dap.model.proto)},

		"%"	:function(value){for(var nodes=value.childNodes,i=nodes.length,n; i-->0;)if((n=nodes[i]).nodeType==1)return n},
		
		GET	:function(value){return daRequest(dap.method.GET,true,value)},
		qookie	:function(value){return getqookie(value)},
		eval	:function(value){return eval(value)}
	},
	
	flatten	:{
		"" 	:function(feed){	// flat hash {@a:v1,@b:v2}
				var hash={};
				for(var i=0;i<feed.length;i++)hash[feed[i].alias]=feed[i].value;
				return hash;	
			},
			
		"*"	:function(feed){	// pre-rowset
				var rows=[];
				for(var i=0;i<feed.length;i++){
					var t=feed[i], alias=t.alias, value=t.value, isRowset=value instanceof Array;
					if(alias)rows=rows.concat(daMapGrid(isRowset?value:[value],alias));
					else if(isRowset)rows=rows.concat(value);
					else rows.push(value);
				}
				return rows;
			},
			
		"nv*"	:function(feed){
				var rows=[];
				for(var i=0;i<feed.length;i++)rows.push({name:feed[i].alias,value:feed[i].value});
				return rows;
			},
			
		"~"	:function(feed){return feed},
		"%"	:function(feed){return daTuckFeed(feed.shift().value,feed)},
		
		"+"	:function(feed){var sum=0,a;for(var i=0;i<feed.length;i++)if(a=feed[i].value)sum+=parseFloat(feed[i].value);return String(sum)},
		"-"	:function(feed){var sub=parseFloat(feed[0].value||"0")*2,a;for(var i=0;i<feed.length;i++)if(a=feed[i].value)sub-=parseFloat(a);return String(sub)},

		any	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value)return feed[i].value;return false},		/// first non-empty
		all	:function(feed){for(var i=0;i<feed.length;i++)if(!feed[i].value)return false; return feed[i-1].value},			/// succeeds if empty token found
		none	:function(feed){return !dap.flatten.any(feed)},
		lack	:function(feed){return !dap.flatten.all(feed)},

		"?@"	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value==feed[i].alias)return true;return false},	/// any match
		"!@"	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value!=feed[i].alias)return false;return true},	/// all match
		
		eq	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value!=feed[0].value)return false;return true},
		ne	:function(feed){for(var i=0;i<feed.length;i++)if(feed[i].value!=feed[0].value)return true;return false},
		dsc	:function(feed){var a=parseInt(feed[0].value);for(var i=0;i<feed.length;i++)if(a<(a=parseInt(feed[i].value)))return false;return true},
		asc	:function(feed){var a=parseInt(feed[0].value);for(var i=0;i<feed.length;i++)if(a>(a=parseInt(feed[i].value)))return false;return true},
		
		like	:function(feed){var match=feed.shift().value; if(match)for(var i=0;i<feed.length;i++)if(match.indexOf(feed[i].value)==0)return true},
		str	:function(feed){var head=feed.shift(); return String[head.alias].apply(String(head.value),feed); },

		
		concat	:function(feed){var string="";for(var i=0;i<feed.length;i++)if(feed[i].value)string+=feed[i].value;return string},
		space	:function(feed){return daConcat(feed," ")},
		csv	:function(feed){return daConcat(feed,",")},	/// x1,x2,x3 (comma-separated values)
		bar	:function(feed){return daConcat(feed,"|")},	/// x1|x2|x3 (bar-separated values)
		nvp	:function(feed){return daFlattenRowsets(feed," :",";")},
		
		url	:function(feed){return daURLEncode(feed)},
		urlstate:function(hash){
				var a=[];
				for(var i in hash)if(hash[i].value)a.push(hash[i].alias+":"+encodeURIComponent(hash[i].value));
				daSetUrl(a.join(";"));
			},
			
		GET	:function(feed){return daRequest(dap.method.GET,true,null,feed)},
		QUE	:function(feed){return daQueuery(feed)},
		
		sibling	:function(feed){
				for(var node=feed.shift().value,i=0;i<feed.length;i++)node=daSibling(feed[i].value,feed[i].alias,node);
				return node;
			},
			
		next	:function(feed){
				for(var head=feed.shift().value,a,i=0;i<feed.length;i++)if(feed[i].value==head)return feed[(i+1)%feed.length].value;
				return feed[0].value;
			},
			
		"pull"	:function(feed){for(var head=feed.shift().value,a,i=0;i<feed.length;i++)if((a=feed[i].value)&&(a=head[a]))return a},
		"case"	:function(feed){for(var head=feed.shift().value,i=0;i<feed.length;i++)if(feed[i].alias==head)return(feed[i].value)},
			
		"pull*"	:function(feed){
				var head=feed.shift().value, a, out=[];
				for(var i=0;i<feed.length;i++)
					if((a=feed[i].alias)){if((a=feed[i].value)&&(a=head[a]))out.push(a)}
					else if(!out.length)out.push(feed[i].value);
				return out;
			},
			
		"case*"	:function(feed){
				var head=feed.shift().value, a, out=[];
				for(var i=0;i<feed.length;i++)
					if((a=feed[i].alias)){if(a==head)out.push(feed[i].value)}
					else if(!out.length)out.push(feed[i].value);
				return out;
			}
	},
 
	operate	:{	/// see dap operators reference at http://dapmx.org/0.2.0/#operate
	
		"!"	:function(value,alias,node,$) {daPhase.d(node,value,$)},
		"%"	:function(value,alias,node,$) {if(alias)value=daMap(value,alias.split(","));for(var i in value)$[0][''][i]=value[i]},
		map	:function(value,alias,node,$) {$[0][''][alias]=daRoute(node,value||alias)},
		"^"	:function(value,alias,node,$) {
				for(var e=alias.split(","),i=e.length;i-->0;)
					for(var entry=e[i],a=null,D=$;D&&!a;D=D[1])
						if((a=D[0][''][entry])!=null)$[0][''][entry]=a;
			},

		"?"	:function(value,alias,node) {if(!value)return O},
		"??"	:function(value,alias,node) {if(alias?alias!=value:!value)return O},
		"?!"	:function(value,alias,node) {if(alias?alias==value:!value)return O},

		u	:function(value,alias,node) {daActivateNode(node,alias,value)},
		ui	:function(value,alias,node) {daActivateNode(node,alias,value,"ui")},
		"u!"	:function(value,alias,node) {if(value==null)value=node;if(value)daPhase.u(value,alias||daDEFAULT_EVENT)},
		capture	:function(value,alias,node) {daActivateNode(node,alias,value,false,true)},
		attr	:function(value,alias,node) {if(value)node.setAttribute(alias,value)},
		
		"*"	:function(value,alias,node) {return value?daMapGrid((value instanceof Array)?value:[value],alias):O},
		"+row"	:function(value,alias,node) {if(value)daSibling(value,alias,node)},
		"-row"	:function(value,alias,node) {if(value)daMoveRow(null,'away',value)},
		move	:function(value,alias,node) {daMoveRow(value,alias,node)},
		append	:function(value,alias,node) {
				var $=node.$[0][''],rows=alias?($[alias]||($[alias]=[])):daLocateRow(node).rows;
				rows.push(value);
			},
			
		qookie	:function(value,alias,node) {setqookie(alias,value)},
			
		a	:function(value,alias,node) {var a=(value||node).P.rules.a; if(a)new daExecBranch(node).exec(a.todo)},
		go	:function(value,alias,node) {if(value)return alias},
		done	:function(value,alias,node) {return (!alias)||value},//false;
		history	:function(value,alias,node) {daHistory(node)}

	},
	
	model	:{	/// data parsing models
		dict	:daL([daDict]),		// one-level dictionary
		flow	:daL([daFlow]),		// one-level sequence
		
		"dict*"	:daL([daDict,true]),	// recursive dictionary
		"flow*"	:daL([daFlow,true]),	// recursive sequence
		
		rset	:daL([daFlow,daDict,true]),		// rowset (sequence of dictionaries)
		book	:daL([daDict,daFlow,daDict,true]),	// dictionary of rowsets
		
		proto	:daL([daTemplate])	// prototype
	},

	method	:{	/// XHR request methods

		GET	:function(req,async,url,feed,contentType){
				req.open("GET", (url||'')+daURLEncode(feed), async);
				req.setRequestHeader("Content-Type",contentType||"text/xml");
				req.send(null);
			},
			
		POST	:function(req,async,url,feed,contentType){
				// if contentType not specified, data is urlencoded; otherwise sent as is
				var post = contentType ? feed : daURLEncode(feed);
				req.open("POST",url,async);
				req.setRequestHeader("Content-Type",contentType||"application/x-www-form-urlencoded");
				// req.setRequestHeader("Content-Length",post?post.length:0);
				req.send(post);
			},
			
		QUEUERY	:function(req,async,url,feed,queueryType){
				if(!queueryType)queueryType="multi";
				var queuery;
				switch(queueryType.toLowerCase()){
					case "onerow":
					case "rowset":
						queuery=daQueueryElement(feed.shift());
						break;
					case "multi":
						queuery=newNode("queuery");
						for(var i=0;i<feed.length;i++)
							if(feed[i].value!=null)
								queuery.appendChild(daQueueryElement(feed[i]));
						break;
					default:
						daFail("Unknown queuery type: "+queueryType,feed)
				}
				// for(var i in qookie)
					// if((a=qookie[i])!=null)
						// queuery.setAttribute(i,a);

				var post=daSerialize(queuery);
				req.open("POST",url,async);
				req.setRequestHeader("Content-Type","text/xml");
				//req.setRequestHeader("Content-Length",post?post.length:0);
				req.setRequestHeader("dap-Queuery",queueryType);
				qookieHeader(req);
				req.send(post);
			}
	}
};


/// Queuery = Queue<Query>

function daQueueryElement(token){
	try{
		return token.alias ? daDumbDOM(token.value,token.alias) : daParseXML(token.value);
	}catch(e){
		daWarn("bad query: "+token.alias,token);
		return newStub(token.value);
	}
}

function daQueuery(){
	this.domains={};
	this.clients={};
};
daQueuery.prototype={

	quey	:0,	// query key

	append	:function(quequest,domain){
			domain=daAbsUrl(domain);
			quequest.query.setAttribute("quey",++quey);
			(this.queries[domain]||(this.queries[domain]=[])).push(quequest.query);
			(this.clients[quey])=quequest.postpone;
		},
	execute	:function(){
			for(var url in this.domains){
				try{daRequest(dap.method.POST,true,url,daSerialize(this.domains[url]),"text/xml")}
				catch(y){y.snapshot.queuery=this}
			}
		},
	dispense:function(response){
			for(var nodes=response.nodes,i=0;i<nodes.length;i++){
				var result=nodes[i], k=result.getAttribute("quekey");
				if(k)result.removeAttribute("quekey"),this.clients[k].postpone.ready(result);
			}
		}
}

function daQuequest(method,dummy,url,feed,contentType){
	daQueuery.append(this,url);
	this.query=newNode("query");
	method(this,async,url,feed,contentType);
	throw new daPostpone(req);
}
daQuequest.prototype={
	open	:function(dummy,url){this.query.setAttribute("url",url)},
	send	:function(post){if(post)this.query.appendChild(newCDATA(post))},
	setRequestHeader
		:function(name,value){
			var header=this.query.appendChild(newNode("header"));
			header.setAttribute("name",name);
			header.setAttribute("value",value);
		}
}


/// Qookie = quick cookie

var qookie={};
function setqookie(name,value){qookie[name]=value};
function getqookie(name){return qookie[name]};

function qookieHeader(req){
	var qoo=[],a;
	for(var i in qookie)if((a=qookie[i])!=null)qoo.push(i+":"+encodeURIComponent(a));
	if(qoo.length)req.setRequestHeader("dap-Qookie",qoo.join("; "));
}





/// handy stuff, that may also be useful for custom namespaces

var isIE=(navigator.appName == 'Microsoft Internet Explorer');	//i hate MSIE

function daTitle(text){document.title=text};
function unslash(str){return str?str.replace(/\\n/g,"\n"):""};

function daEncode64(str){return Base64.encode(str)};
function daDecode64(str){return Base64.decode(str)};


/// HTML node manipulation shortcuts
function newElem(e){return document.createElement(e)};//try{}catch(er){alert("newElem fail:"+e)}
function newText(t){return document.createTextNode(t)};
function newElemText(e,t){var e=newElem(e);if(t)e.appendChild(newText(t));return e};
function newStub(c){return document.createComment(c)};
function insertBefore(newnode,refnode){if(!refnode)return;refnode.parentNode.insertBefore(newnode,refnode)};
function insertAfter(newnode,refnode){if(!refnode)return;if(refnode.nextSibling)refnode.parentNode.insertBefore(newnode,refnode.nextSibling);else refnode.parentNode.appendChild(newnode)};
function insertInstead(newnode,refnode){if(!refnode)return;refnode.parentNode.replaceChild(newnode,refnode)};
function daCut(node){return node.parentNode?node.parentNode.removeChild(node):node};

function enstyle(node,cls){
	if(!node.className)return node.className=cls;
	else if(node.className.indexOf(cls)<0)return node.className+=" "+cls;
};
function destyle(node,cls){
	var c=node.className;
	if(c&&c.indexOf(cls)>=0)return c!=(node.className=c.replace(new RegExp("\\b"+cls+("\\b"),"g"),"").replace("  "," "));
};

daAddEventListener=
	document.addEventListener?function(node,event,handler,capture){node.addEventListener(event,handler,capture||false)}:
	//document.attachEvent?function(node,event,handler){node.attachEvent(event,handler,false)}:
	function(node,event,handler,capture){node["on"+event]=handler};

	
/// ui highlight
function evStop(e){if(e&&e.stopPropagation)e.stopPropagation();else event.cancelBubble=true};
function daOver(e){evStop(e);enstyle(this,"over")};
function daOut (e){evStop(e);destyle(this,"over")};

function daActivateNode(node,alias,value,hilite,capture){
	if(!alias)alias=daDEFAULT_EVENT;
	daAddEventListener(node,alias,daEvent);
	
	if(!value)value=node.P;
	if(!value.rules)value.engage();
	(node.rules||(node.rules={}))[alias]=value.rules.u;

	if(hilite&&node.className.indexOf(hilite+" ")<0){
		enstyle(node,hilite);
		daAddEventListener(node,"mouseover",daOver,capture||false);
		daAddEventListener(node,"mouseout",daOut,capture||false);
	}
}


/// XML(non-HTML) node shortcuts

var msxml="Microsoft.XmlDom"; // MsXml2.DOMDocument ?? or what???

var XMLdocument;
	if(window.DOMParser)XMLdocument=new DOMParser().parseFromString("<xml/>","text/xml");
	else {
		XMLdocument=new ActiveXObject(msxml);
		XMLdocument.async=false;
		XMLdocument.loadXML("<xml/>");
	}

function newNode(e){return XMLdocument.createElement(e)};
function newTextNode(t){return XMLdocument.createTextNode(t)};

daParseXML=window.DOMParser		/// XML -> DOM
	?function(str){
		var x=new DOMParser().parseFromString(str,"text/xml").documentElement;
		return (x.nodeName!="parsererror")?x:newTextNode("value",str);
	}
	:function(str,alt){
		var x=new ActiveXObject(msxml); 
		x.async=false;
		x.loadXML(str);
		return (x.parseError.errorCode)?newTextNode("value",str):x.documentElement; 
	};

//function daSerialize(node){
daSerialize=window.XMLSerializer	/// DOM -> XML
	?function(node){return new XMLSerializer().serializeToString(node)}
	:function(node){
		return node.xml;
	};
	
daPrettyPrint=window.XML
	?function(xml){return new XML(xml).toXMLString()}
	:function(xml){return xml};

function daDumbDOM(obj,tag){		/// build a simple xml document from obj hierarchy
	var node=newNode(tag);
	if(obj instanceof Object)for(var i in obj)
		if(obj[i])node.appendChild(daDumbDOM(obj[i],i));
		else;
	else if(obj)node.appendChild(newTextNode(String(obj)));
	return node;
};



/// Url handling
							
function daQueryString(){		/// extract page arguments from its URL # part
	var hash={},href=window.location.href.split('#')[1],hs,nv;
	if(href)for(var i in hs=href.split(';'))nv=hs[i].split(':'),hash[nv[0]]=decodeURIComponent(nv[1]);//unescape()
	return hash;
};

function daAbsUrl(url,base){		/// evaluate absolute URL from relative to base URL
	if(url==null)return;
	if(/^\w*:/.test(url))return url;
	
	var	a=base||window.location.href,//location.href, //
		protocol=(a=a.split("//"))[0],
		hostname=(a=a[1].split("/")).shift(),
		filename=a.pop(),
		pathname="/"+(a.length?(a.join("/")+"/"):"");
	
	var a=protocol+"//"+hostname;
	if(url.charAt(0)=="/")return a+url;
	a+=pathname.replace(/\/[^\/]*$/,'');
	var up=url.match(/\.\.\//g);
	if(!up)return a+"/"+url;
	a=a.split("/").slice(0,-(up.length));
	a.push(url.substr(up.length*3));
	return a.join("/");
};

function daURLEncode(feed){		/// convert feed to urlencoded string. anonymous tokens appended unencoded
	var url="",a,v;
	if(feed)for(var i=0;i<feed.length;i++)
		if(v=feed[i].value){
			if(v instanceof Object)v=daSerialize(daDumbDOM(v,feed[i].alias));
			url+=(a=feed[i].alias)?"&"+a+"="+encodeURIComponent(v):v;
		}
	return url;
};

function daNormalizeUrl(remap){		/// normalize url ?foo=1&bar=2#foo=3 to #foo:3;bar:2
	var	full=window.location.href.split("#"),
		base=full[0].split("?"),
		qstr=base[1];
		
	if(qstr){
		var	hash=daQueryString(),		// take args from URL #-part
			qs=qstr&&qstr.split("&"),	// merge with args from ?-part
			re=null;			// and optionally remap
		
		//url params remapping
		if(remap&&(re={})) // "page@g article@a critn@c count@n item@i ..."
			for(var keys=remap.split(' '),i=keys.length; i-->0;){
				var pair = keys[i].split('@');
				re[pair[0]]=pair[1];
			}
		
		for(var i=qs.length;i-->0;){
			var nv=qs[i].split("="), key=(re&&re[nv[0]])||nv[0];
			try{ hash[key]=hash[nv[0]]||decodeURIComponent(unescape(nv[1])) }
			catch(e){ daWarn("Bad URI",e) }
		}
		
		daSetUrl(null,hash);
	}
	
	return qstr||false; // false if no normalization was needed
};
	

/// Url history

var urlstate=window.location.href.split('#')[1],historian=null;

function daHistory(node){
	if(historian)daFail("only one historian allowed");
	historian=node;
	setInterval(function (){if( urlstate != (urlstate=window.location.href.split("#")[1]) )daPhase.u(historian,daDEFAULT_EVENT)},200);
}

function daSetUrl(querystring,queryhash){
	if(queryhash){
		var norm=querystring?[querystring]:[];
		for(var i in queryhash)norm.push(i+":"+encodeURIComponent(queryhash[i]));
		querystring=norm.join(";")
	}
	var href=window.location.href.split('#')[0].split('?')[0]+"#"+(urlstate=querystring);
	if(window.location.href!=href)window.location.href=href;
	return urlstate||null;
};



/// http request. Method implementations are defined in dap.method section

var daASYNC=false; // async queries are only allowed on d-phase

function daRequest(method,async,url,feed,contentType){
	var req=window.XMLHttpRequest?new XMLHttpRequest():new ActiveXObject('Msxml2.XMLHTTP');
	async=!isIE&&async&&daASYNC&&!daContexts.length;//
	if(async)req.onreadystatechange=xhrProgress;
	method(req,async,url,feed,contentType);
	if(async)throw new daPostpone(req);
	else return xhrGetXML(req);
};


/// async XHR querying

function daPostpone(req){
	this.snapshot={};
	if(isIE)ieXHRsucks.push(req={xhr:req,postpone:this});
	else req.postpone=this;
};
daPostpone.prototype={
	ready	:function(value){
			this.value=value;
			new daPostJob(daResume,this);
		}
};
function daResume(postponed){
	var y=postponed.snapshot;
	if(!y)daFail("bad postponed",postponed);
	if(y.execBranch){
		y.target.value=postponed.value;
		y.execBranch.exec(y.todo);
	}
	if(y.queuery){
		y.queuery.dispense(postponed.value);
	}
}


function xhrProgress(){
	if(this.readyState!=4)return;
	var req=isIE?ieXHRsuck(this):this;
	if(req.postpone)req.postpone.ready(xhrGetXML(this));
	else daFail("no postpone",req);//,this
};
function xhrGetXML(req){
	var xml;
	if(req.status==200)
		try{xml=req.responseXML?req.responseXML.documentElement:req.responseText}
		catch(e){xml=daErrorXML}
	else daWarn("Problem with URL: ["+req.daURL+"]\n",req);
	return xml||emptyXML;
};

var ieXHRsucks=[], daErrorXML=newNode("ERROR");

function ieXHRsuck(xhr){
	for(var i=ieXHRsucks.length;i-->0;)if(ieXHRsucks[i].xhr==xhr){var a=ieXHRsucks[i];ieXHRsucks.splice(i,1);return a}
};

///

function daClone(value){
	if(value instanceof Object){
		var prop={};
		for(var i in value)if(value.hasOwnProperty(i))prop[i]=daClone(value[i]);
		prop.prototype=value.prototype;
	}else return value;
}


/// string works

function daTrim(str){			/// trim padding whitespaces
	if(str&&str.replace)return str.replace(/^\s+|\s+$/g,"")
};
function daReplaceMulti(value,regs){	/// multiple regex replaces
	if(value)for(var i=regs.length;i-->0;)value=value.replace(regs[i][0],regs[i][1]);
	return value;
};
function daTuck(str,value,alias){	/// php-like {entry} substitution
	if(value instanceof Object && !(value instanceof String))for(var i in value)str=daTuck(str,value[i],i);
	else str=str.replace(new RegExp('\\{'+alias+'\\}','g'),value);
	return str;
};
function daTuckFeed(str,feed){	
	for(var i=0;i<feed.length;i++)str=daTuck(str,feed[i].value,feed[i].alias);
	return str.replace(/\{.*?\}/g,'');
};			
function daConcat(feed,separator){	/// concatenate feed tokens
	var a,out=[];
	for(var i=0;i<feed.length;i++)if(a=feed[i].value)out.push(a);
	return out.join(separator);
};
function daSplit(value,separator){	/// recursive split - string to rowset conversion
	if(value instanceof Array)for(var i=0;i<value.length;i++)value[i]=daSplit(value[i],separator);
	else if(value&&value.split)value=value.split(separator);
	return value;
};
function daFlattenRowsets(feed,fsep,rsep){
	var out=[];
	for(var i=0;i<feed.length;i++)if(feed[i]){
		var rows=feed[i].value, flds=feed[i].alias;
		if(flds)flds=flds.split(",");
		if(rows)for(var r=0;r<rows.length;r++){
			var row=rows[r], unrow=[];
			if(flds)for(var f=0;f<flds.length;f++)unrow.push(row[flds[f]]);
			else for(var f in row)unrow.push(row[f]);
			out.push(unrow.join(fsep));
		}
	}
	return out.join(rsep);
};


/// rowset works
function daMap(datarow,columns){
	var entry={};
	if(datarow instanceof Array)for(var i=datarow.length;i-->0;entry[columns[i]]=datarow[i]);
	else entry[columns[0]]=datarow;
	return entry;
};
function daMapGrid(grid,alias){
	if(!alias)return grid;
	var rowset=[];
	var columns=alias.split(",");
	for(var row=0; row<grid.length; row++)rowset.push(daMap(grid[row],columns));
	return rowset;
};
function daLocateRow(node){
	var row=node.$[0][''],rows=node.$[2];
	while(rows==node.parentNode.$[2])node=node.parentNode;
	for(var i=rows.length; i>=0&&rows[i]!=row; i--);
	if(i>=0)return{ base:node, rows:rows, row:row, index:i };
};
function daMoveRow(value,alias,node){
	var r=daLocateRow(node);
	if(r){
		var base=r.base, row=r.row, rows=r.rows, i=r.index;
		switch(value){
			case'up'	:if(i>0) rows[i]=rows[i-1], rows[i-1]=row, insertBefore(base,base.previousSibling);break;
			case'down'	:if(i<rows.length-1) rows[i]=rows[i+1], rows[i+1]=row, insertAfter(base,base.nextSibling);break;
			case'top'	:rows.splice(i,1), rows.unshift(row), insertBefore(base,base.parentNode.firstChild);break;
			case'bottom'	:rows.splice(i,1), rows.push(row), insertAfter(base,base.parentNode.lastChild);break;
			case'away'	:rows.splice(i,1), base.parentNode.removeChild(base);
		}
	}
};
function daSibling(value,alias,node){
	var r=daLocateRow(node);
	if(r){
		var	base=r.base, rows=r.rows, i=r.index,
			sibling=base.P.spawn(base.parentNode,[{'':value},base.$,rows]);
		if(sibling)switch(alias){
			case "instead"	:rows[i]=value;insertInstead(sibling,base);break;
			case "before"	:rows.splice(i,0,value),insertBefore(sibling,base);break;
			case "after"	:
			default		:rows.splice(i+1,0,value),insertAfter(sibling,base);break;
		}
		return sibling;
	}
};


/// core data models
function daFlow(node,model){
	if(node.nodeType!=1)return node;
	var flow=[],nodes=node.childNodes,a,m0,m1,t;
	if(model)
		if(model[0]==true)m0=daFlow,m1=model;
		else m0=model[0],m1=model[1];
	for(var a=node.attributes,i=0;i<a.length;i++)
		flow[t=a[i].nodeName.toLowerCase()]=a[i].nodeValue;
	if(nodes)for(var i=0;i<nodes.length;i++){
		var n=nodes[i];
		switch(n.nodeType){
			case 1:
				a=m0?m0(n,m1):n;
				flow.push(a);
				break;
			case 3:
			case 4:if((a=n.nodeValue).replace(spaces,""))flow.push(a);break;
		}
	}return flow;
};
function daDict(node,model){
	if(node.nodeType!=1)return node;
	var dict={},nodes=node.childNodes,t=null,text="",a,m0,m1,t;
	if(model)
		if(model[0]==true)m0=daDict,m1=model;
		else m0=model[0],m1=model[1];
		
	for(var a=node.attributes,i=0;i<a.length;i++)
		dict[t=a[i].nodeName.toLowerCase()]=a[i].nodeValue;
	if(nodes)for(var i=0;i<nodes.length;i++){
		var n=nodes[i];
		switch(n.nodeType){
			case 1:	t=n.nodeName.toLowerCase();
				a=m0?m0(n,m1):n;
				if(dict[t])((dict[t]instanceof Array)?dict[t]:(dict[t]=[dict[t]])).push(a);
				else dict[t]=a;
				break;
			case 3:
			case 4:if((a=n.nodeValue).replace(spaces,""))text+=daTrim(a);break;
		}
	}
	if(!t)return text;
	if(text)dict[""]=text;
	return dict;
};
function daTemplate(node,inline){
	var content=daFlow(node,dap.model.proto);
	if(content.length<2)content=content.length?content[0]:null;
	return (inline&&!node.attributes.length)?content:new daProto(inline,node,content);
};

function daL(a){for(var i=a.length,L=[a[--i]];i-->0;L=[a[i],L]);return L};
function daModel(node,model){return model[0](node,model[1])};
function daPickModel(dapmodel,dapns){return daReachNS(dapmodel,dap.model,dapns)||daFail("Can't resolve model: "+dapmodel,dapns)};


/// uniform node notation support
function daUniform(node){ // convert classed node to dap uniform notation
	var tag=node.tagName.split("."),c=node.getAttribute("class");
	if(tag[0]=='o')tag[0]=false; //<o> refers to default element
	if(c)tag=tag.concat(c.split(" ")),node.removeAttribute("class");
	return tag;
};
function daNativeNode(tag){ // create a native (HTML) node from dap uniform notation
	node=document.createElement(tag.shift()||daDEFAULT_ELEMENT);
	if(tag.length)node.className=tag.join(" ");
	return node;
};

var O=[], emptyPath=[''], emptyXML=document.createElement('empty'), spaces=/\s+/g;


//dap engine (compressed)
function daChain(chainstring){return chainstring.split(",")};
function daPath(pathstring){
	if(!pathstring)return emptyPath;
	var path=pathstring.split(".");
	return path.length>1?path.reverse():path;	
};

function daPut(obj,path,value){
	for(var i=path.length,alias=path[--i];i-->0;obj=obj[path[i]]||(obj[path[i]]={}));
	obj[alias]=value;
};

function daReach(path,o){for(var i=path.length;o&&(i-->0);o=o[path[i]]);return o};
function daRoute(o,str,value){
	var path=str?daPath(str):o;
	if(value==null)return str?daReach(path,o):o;
	if(path){
		var entry=path.shift();
		(path.length?daReach(path,o):o)[entry]=value;
	}
	return value;
};


/// namespaces

var	dapnsRegistry={}, dapnsStack=[{}], dapx={};

function daNS(href){
	this[""]=href;
	if(!dapnsRegistry[href])dapnsRegistry[href]=this;
	else daFail("duplicate namespace: "+href);
}

function daNamespace(node,href,base){

	//if(!node.getAttribute)alert("635:"+node);

	var	inherit=(href.charAt(0)=="#")?dapnsStack[0]:null,
		imports=node.getAttribute("dapns-import"),
		model,ihref,a;
		
	if(inherit){
		ihref=inherit[""];
		href=ihref+href;
		for(var i in dap)if(a=dap[i][ihref])dap[i][href]=a;
	}
	else href=daAbsUrl(href,base);
	
	var	tgtns= dapnsRegistry[href] || new daNS(href), //target dapns
		refns=inherit||tgtns;
	
	if(imports)daImportNS(refns,imports);
	if(a=node.getAttribute("dapmodel"))model=daPickModel(a,refns);
	
	dapnsStack.unshift(refns); //reference dapns
	
	for(var a=node.attributes,t,i=a.length;i-->0;)tgtns[t=a[i].nodeName.toLowerCase()]=a[i].nodeValue;
	for(var nodes=node.childNodes,i=0,n,alias;n=nodes[i];i++)
		switch(n.nodeType){
			case 1:	alias=n.nodeName.toLowerCase();
				if(!tgtns[alias])tgtns[alias]=
					(a=n.getAttribute("dapns"))?daNamespace(n,daTrim(a),href):
					(a=n.getAttribute("dapmodel"))?daModel(n,daPickModel(a,refns)):
					model?daModel(n,model):
					daTemplate(n,true);
				else	if(a=n.getAttribute("dapns"))n=daNamespace(n,daTrim(a),href); 
					//else daFail("Duplicate "+alias+" in "+href,tgtns);
				break;
			case 3:
			case 4: if(daTrim(a=n.nodeValue))daPlugJS(a,href);break;
		}
		
	dapnsStack.shift();
	
	return tgtns;
};

function daPlugJS(js,href){
		var a=eval(js);
		if(a)for(var i in a)if(dap[i]){
			var o=dap[i][href];
			if(o)for(var j in a[i])o[j]=a[i][j];
			else dap[i][href]=a[i];
		}
	// try{
	// }catch(e){daFail("Namespace "+href+" has invalid js: "+e.message,js)}
}

function daImportNS(dapns,imports){
	if(!imports)return;
	imports=daTrim(imports).replace(/\s*::\s*/g,"::").split(spaces);
	for(var i=0; i<imports.length; i++){
		var	a=imports[i].split("::"),
			alias=a[0], base=dapns[""],
			href=daAbsUrl(a[1],base);
			present=dapns[alias];
		if(!alias)daRequireNS(dapnsRegistry[href] || new daNS(href));
		else if(!present)dapns[alias]=dapnsRegistry[href] || new daNS(href);
		else if(daAbsUrl(present.dapns,base)!=href)
			daFail("Duplicate '"+alias+"' in "+dapns.dapns+" : "+href,dapns);
	}
};
function daRequireNS(dapns){
	if(!dapns.dapns){ //if dapns have been loaded already, it should have had "dapns" attribute
		var	href=dapns[""]||daFail("No href for this entry",dapns),
			xml=daRequest(dap.method.GET,false,href,null,"text/xml") || daFail("Cannot load namespace:"+href);
		daNamespace(xml,href);
	}
	return dapns;
};
function daSeekNS(path,dapns){
	var alias=path.pop(), entry=dapns[alias]||daFail("Namespace not found: "+alias, dapns);
	if(entry[""])daRequireNS(entry);
	return path.length?daReach(path,entry):entry;
};
function daReachNS(pathstring,domain,dapns){
	var path=pathstring.split("."),alias=path[0],a;
	if(path.length>1){
		if(alias)dapns=daRequireNS(dapns[alias]||daFail("Namespace not found: "+alias,dapns));
		path[0]=dapns[""];
		return daReach(path.reverse(),domain);
	}else return (a=domain[dapns[""]])&&a[alias] || domain[alias];
};
function daChainReachNS(dapns,paths,domain,chain){
	for(var paths=daChain(paths),i=paths.length,a; i-->0; )
		if(a=daReachNS(paths[i],domain,dapns))chain=[a,chain];
		else return;
	return chain;
};


// Parse

var	daWS	=/[\s\r\n\t]+(?:\/\*.*?\*\/\s*)*/g, // C-style /* comments */
	daBOPN	=/[\(\{\<\[][\s;]*/g,
	daBCLS	=/[\s;]*[\]\>\}\)]/g,
	
	daJUNK	=new RegExp([
		"(?://\\*.*$)",	// comments to //* end-of-rule 
		"\\s*(?=\\=)",	//whitespace in front of assignment sign
		"^[;\\s]+",	//leading spaces and semicolons
		"[;\\s]+$"	//trailing semicolons
		].join("|"),"g"),
	
	daINHRT	=":: ",				//inherit rule
	daBRCKT	=/\(([^\(\)]*)\)/g,		//bracket contents
	daSTEPS	=/\s*;\s+/,
	daTOKEN =/,?\s+/,

	daBrackets=[],
	daContexts=[];

function daCutBracket(outer,inner){return"<"+daBrackets.push(inner)+">"};
function daPickBracket(key){return daContexts[0].bckts[parseInt(key.replace(/\D/g,""))]};


function daRunChildrenStep(content){
	this.operator=dap.operate["!"];
	this.feed=[{value:content}];
};
function daApplyAttrsStep(attrs){
	this.operator=dap.operate.attr;
	this.feed=[];
	alert("attrs:"+attrs);
	for(var i=attrs.length; i-->0; ) // ie!! danger!!!
		this.feed.push({alias:attrs[i].name,value:attrs[i].value});
};

function daProto(inline,node,content){
	this.content	=content;
	this.dapns	=dapnsStack[0];
	this.xml	=node.cloneNode(false);
	if(!inline)	this.node=daNativeNode(daUniform(node));
};
daProto.prototype={

	engage:function(){
		daContexts.unshift({dapns:this.dapns,proto:this});
		var a, chi=this.content&&[new daRunChildrenStep(this.content)], xml=this.xml;
		this.rules={
			u:(a=xml.getAttribute("u"))&&new daRule(a),
			a:(a=xml.getAttribute("a"))&&new daRule(a),
			d:((a=xml.getAttribute("d"))||chi)&&new daRule(a,chi)
		};
		
		if(!isIE){/* can't beat ie fancy attributes and lame innerHTML */ 
		// for(var a in this.rules)xml.removeAttribute(a);
		// if(xml.attributes.length){
			// var a=new daApplyAttrsStep(xml.attributes);
			// if(this.rules.d)this.rules.d.pre(a);
			// else this.rules.d=new daRule(a);
		// }
		}
		daContexts.shift();
		return this.rules;
	},
	
	spawn:function(node,$){
		var d=(this.rules||this.engage()).d,empty;
		
		if(!$)$=node.$;
		
		if(this.node){
			node=this.node.cloneNode(false),
			node.$=d&&d.def?[{'':$[0]['']},$,$[2]]:$,
			node.P=this;
		}
		else if(d&&(d.use||d.def))daWarn('Entry references are discarded in inlines',this);
		
		if(d)empty=(new daExecBranch(node)).exec(this.rules.d.todo)&&!node.childNodes.length;
		if(this.node)return(empty&&!node.childNodes.length)?daMute(node):node;
	}
};
function daRule(rulestr,todo){

	if(rulestr){
		var	c=daContexts[0],
			a=daTrim(rulestr).split(daINHRT),
			inherit=a[1];
			
		if(a[1]){
			if(todo)for(var r=todo;r[1];r=r[1]);
			var	inherit=daSeekNS(daPath(a[1]),c.dapns) || daFail("Can't inherit: "+rulestr,c.dapns),
				rule=(inherit instanceof Array)?null:(inherit.rules||inherit.engage()).d,
				inhtodo=rule?rule.todo:[new daRunChildrenStep(inherit)];
			
			if(r)r[1]=inhtodo; else todo=inhtodo;
			if(rule){
				for(var i in rule.def)(this.def||(this.def={}))[i]=true;
				for(var i in rule.use)(this.use||(this.use={}))[i]=true;
			}
		}
		if(a=a[0].replace(daWS," ").replace(daBOPN,'(').replace(daBCLS,')').replace(daJUNK,"")){
			if(a.indexOf("--")==0)alert("["+a+"]");
			c.rulestring=a;
			
			daBrackets=[];
			while(a!=(a=a.replace(daBRCKT,daCutBracket)));
			daBrackets.unshift(a);
			
			c.bckts=daBrackets;
			c.use=null;
			c.def=null;
			todo=daList(daStep,a.split(daSTEPS),todo);
			for(var i in c.def)if(i)(this.def||(this.def={}))[i]=true;
			for(var i in c.use)if(i)(this.use||(this.use={}))[i]=true;
		}
	}
	this.todo=todo;
};
daRule.prototype={
	pre	:function(step){if(step)this.todo=[step,this.todo]}
}


function daStep(a){
	if(!a)return null;
	if(/^<\d*>$/.test(a))this.branch=daList(daStep,daPickBracket(a).split(daSTEPS),null);
	else{
		a=a.split(daTOKEN);
		if(!/[$<=]/.test(a[0])){// operator:convert@alias
			var h=a.shift();
			if((h=h.split("@")).length>1)this.alias=h[1];
			if((h=h[0].split(":")).length>1)this.convert=daConverts(h[1]);
			this.operator=(h[0]=="")?O:daReachNS(h[0],dap.operate,daContexts[0].dapns) ||daFail("missing operator "+h,daContexts[0].dapns);
		}
		this.feed=a.length?daVector(daToken,a.reverse(),this):[{alias:this.alias,convert:this.convert}];
	}
};
function daToken(a,step){

	if((a=a.split("`")).length>1)this.liter=a.length>2?unescape(a[2]):a[1];
	if((a=a[0].split("@")).length>1)this.alias=a[1];
	else if(step)this.alias=step.alias;
	
	var parts=a[0].split("=");
	
	//rvalue
	if(a=parts.pop())this.daLvalue(a,true,step&&step.convert);
	else this.liter=this.liter||'';
	
	if(this.liter&&this.convert)for(var convert=this.convert;convert=convert[1];)this.liter=convert[0](this.liter);
	
	//lvalues
	if(parts.length)this.lvalues=daList(daLvalue,parts);
	
	if(this.path==true) // $entry=. → $entry=.entry
		a=(this.lvalues||daFail("Illegal shorthand",this))[0].path,
		this.path=[a[0]||a[1]||daFail("Illegal shorthand",this),''];// ? this.path=a ?

	if(this.alias==null)this.alias=(this.path&&this.path[0]);
};
function daLvalue(a,use,convert){
	var c=daContexts[0],flatten=null;
	if((a=a.split(":")).length>1)convert=daConverts(a[1],convert||null);
	if((a=a[0].split(">")).length>1){
		flatten=!a[1]?dap.flatten[""]:daReachNS(a[1],dap.flatten,c.dapns)||daFail("missing flattener "+a[1],c);
		if((a=a[0].split("<"))[1])this.feed=daVector(daToken,daPickBracket(a[1]).split(daTOKEN).reverse());
	}
	if(flatten||convert)this.convert=[flatten,convert]; //flattener as 0-order convertor, to unify async resumes
	if(a=a[0])
		switch(a.charAt(0)){
	
		case".":this.path=(a==".")||daPath(a);break;
			
		case"$":a=daPath(a.substr(1));
			this.path=a;
			var entry=[a[a.length-1]];
			if(use)(c.use||(c.use={}))[entry]=true;
			else if(a.length==1)(c.def||(c.def={}))[entry]=true;
			break;
			
		case"#":if(a=a.substr(1))switch((a=daPath(a)).pop()){
				case''		:this.path=a;a.push(null);break;
				case'content'	:this.liter=this.content;break;
				default		:daFail('unknown mesh',this);
			}else this.path=[null];
			break;
			
		default:a=daPath(a);
			if(this.alias==null)this.alias=a[0];
			this.value=daSeekNS(a,c.dapns);
	}
};
daToken.prototype.daLvalue=daLvalue;

function daVector(daClass,a,step){
	if(!a.length)return null;
	for(var i=a.length;i-->0;)a[i]=new daClass(a[i],step);
	return a;
};
function daList(daClass,a,tail){
	for(var i=a.length;i-->0;)tail=[new daClass(a[i]),tail];
	return tail;
};
function daConverts(a,tail){
	return daChainReachNS(daContexts[0].dapns,a,dap.convert,tail) ||daFail("missing converter "+a,daContexts[0]);
};
function daMute(node){var n=newStub(node.nodeName);n.P=node.P;n.$=node.$;return n};


// Execute
var daStackDepth=0;

function daExecBranch(node,$,up){
	this.node=node;
	this.$=$||node.$;
	this.up=up;
};
daExecBranch.prototype={

	exec:
	function(todo){
		try{
			return this.execBranch(todo);
		}
		catch(y){
			if(y instanceof daPostpone)y.snapshot.execBranch=this;
			else throw(y); // regular error
		}
	},

	execBranch: //returns true if empty
	function(todo){
		if(++daStackDepth>100)daFail("Suspicious recursion depth",this);
		try{
			var node=this.node, $=this.$, empty=true;
			while(todo){
				var step=todo[0],result=null;
				if(step.branch)new daExecBranch(node,null,this.up).execBranch(step.branch);// throws {branch}
				else{
					var	operator=step.operator,
						feed=this.execFeed(step.feed,[]); //throws {feed}
					if(operator)
						for(var i=0;i<feed.length;i++)
							if(result=operator(feed[i].value,feed[i].alias,node,$))
								if(result instanceof Array)
									for(var r=0;r<result.length;r++)
										empty=new daExecBranch(node,[{'':result[r]},$,result],this.up).exec(todo[1])&&empty;
				}
				todo=!result&&todo[1];
			}
		}
		catch(y){
			if(y instanceof daPostpone)
				y.snapshot={
					target	:y.snapshot.target,
					todo	:[
							{
								branch:y.snapshot.todo,
								operator:operator,
								feed:y.snapshot.token.feed
							},
							todo[1]
						]
				}
			throw(y);
		}
		daStackDepth--;
		return result&&(result instanceof Array)?empty:result;
	},

	execFeed:
	function(feed,collect,flatten,proto)
	{
		if(!feed)return;
		var i=feed.length;
		try	{
			if(collect){
				while(i-->0)collect.push({alias:feed[i].alias,value:this.execToken(feed[i])}); // throws {token}
				if(flatten)collect=flatten(collect); //throws {async}
				if(proto)daPhase.d(this.node,proto,[{'':collect},this.$,this.$[2]]);
			}
			else while(i-->0)this.execToken(feed[i]); // throws {token};
			return collect;
		}
		catch(y){
			if(y instanceof daPostpone){
				if(!y.snapshot.target)	//async flattener
					y.snapshot.target=y.snapshot.token={proto:proto};
				else{ //async exectoken
					if(!collect)collect=[];
					collect.push(y.snapshot.token);
					while(i-->0)collect.push(feed[i]);
					y.snapshot.token={
						feed:collect,
						flatten:flatten,
						proto:proto
					}
				}
			}
			throw(y);
		}
	},
	
	execToken:
	function(token)
	{
		var value=token.value,a,c;
		
		if(a=token.path){
			var i=a.length, entry=a[--i];
			if(entry){
				if(this.up){
					value=this.up[entry];
					if(value==null)for(var $=this.$;$&&(value=$[0][entry])==null;)$=$[1];
				}
				else value=daTrace(this.$,entry);
				if(value==null)daFail("Entry not found: "+entry,this);
			}
			else value = (entry==null) ? this.node : this.$[0][''];
			while(value&&(i-->0))value=value[a[i]];
		}
		
		try	{
			var lvalues=token.lvalues,convert,flatten;
			convert=token.convert, flatten=token.feed&&convert[0];
			if(flatten)value=this.execFeed(token.feed,[],flatten,value||token.value);//throws{feed,flatten,proto}
			if( value==null && token.liter!=null )value=token.liter;
			else{
				if(value==null)value='';
				if(convert)while(convert=convert[1])value=convert[0](value); //throws{value/target}
			}

			while(lvalues){
				var lvalue=lvalues[0];
				
				if(a=lvalue.path){
					var	i=a.length,
						entry=a[i-1],
						up=entry&&this.up,
						$=this.$,
						target;
					if(entry==null)target=this.node,i--;
					else{
						if(up||i>1)while($[0][entry]==null)$=$[1] ||daFail(entry+" not declared",this);
						target=$[0];
					}
					while(0<--i)target=target[a[i]]||(target[a[i]]={});
					if(target[a=a[0]]!=value||(up&&up[entry]!=null)){
						target[a]=value;
						if(up)up[entry]=$[0][entry];
					}
				}
				
				convert=lvalue.convert, flatten=lvalue.feed&&convert[0];
				if(flatten)value=this.execFeed(lvalue.feed,[],flatten,value);//throws{feed,flatten,proto}
				if(convert)while(convert=convert[1])value=convert[0](value);//throws{value/target}

				lvalues=lvalues[1];
			}
			return value;
		}
		catch(y){
			if(y instanceof daPostpone){
				if(!y.snapshot.target)y.snapshot.target=y.snapshot.token={}; // fresh async token
				y.snapshot.token.alias=token.alias;
				y.snapshot.token.convert=convert;
				y.snapshot.token.lvalues=lvalues;
			}
			throw(y);
		}
	},
	
 	checkUp:
	function(event,from){
		daASYNC=false;
		var	node=this.node, P=node.P, def=P.rules.d.def, up={},
			rule=(event==true)?null:(node.rules&&node.rules[event])||P.rules.u;
		if(rule)event=this.exec(rule.todo)||event;
		for(var entry in this.up)if(!(def&&def[entry]))up[entry]=this.up[entry];
		var sift = (node.parentNode && node.parentNode.P)?
			new daExecBranch(node.parentNode,null,up).checkUp(event,node):{};
		if(sift)for(var i in sift)this.up[i]=sift[i];//if(!(def&&def[i]))
		return sift && daCheckDown(node,this.up,from);
	}
	
};

function daCheckDown(node,up,from){
	var d=node.P.rules.d; if(!d)return;
	var $0=node.$[0], def=d.def, use=d.use, acc=node.P.rules.a, sift;
	var rebuild=false, repaint=false; //clear=true;

	for(var entry in up)
		if($0[entry]!=null)
		{
			$0[entry]=((sift||(sift={}))[entry]=up[entry]);
			if(use&&use[entry])rebuild=true;
			else if(acc&&acc.use[entry])repaint=true;
		}
	if(rebuild){
		daASYNC=true;
		var rebuilt=node.P.spawn(node.parentNode,node.$);
		try{if(node.parentNode)node.parentNode.replaceChild(rebuilt,node)}
		catch(e){/* too late, forget it */};
	}else{
		if(repaint)new daExecBranch(node).exec(acc.todo);
		if(sift)
			for(var nodes=node.childNodes,i=nodes.length,n;i-->0;)
				if((n=nodes[i]).P && n!=from )daCheckDown(n,sift);
		return sift||{};
	}
};

function daTrace($,entry){
	if($)return($[0][entry]==null)?($[0][entry]=daTrace($[1],entry)):$[0][entry];
};

var daPhase={
	
	d:function(place,P,$){
		var n;
		if(P instanceof Array)for(var i=0;i<P.length;i++)daPhase.d(place,P[i],$);
		else if(P&&(n=P.spawn?P.spawn(place,$):P.nodeType?P:newText(P)))place.appendChild(n);
	},
	
	u:function(node,event){
		new daExecBranch(node,null,{}).checkUp(event);
	}
};

function daEvent(e){
	if(!e)e=window.event;
	evStop(e);
	postprocess=new daPostProcess();
	daPhase.u(this,e.type);
	postprocess.run();
	return true;
}



/// postprocess - handle stuff after DOM has reflown

function daPostProcess(){
	this.queue=[];
	this.phase=0; // ready to fill
};
daPostProcess.prototype={

	put	:function(job,before){
			before?this.queue.unshift(job):this.queue.push(job);
			if(this.phase==2)this.run(); // afterdone
		},
		
	run	:function(){
			var job;
			if(this.phase==1)return;
			this.phase=1; // being executed
			while(job=this.queue.shift())
				if(job.exec())// true -> suspend execution of the rest of the queue
					return this.phase=0; // buffering
			this.phase=2; // done
		}
};

var postprocess=new daPostProcess();

function daPostJob(handler,subject,before){
	this.handler=handler;
	this.subject=subject;
	postprocess.put(this,before);
};
daPostJob.prototype={
	exec	:function(){return this.handler(this.subject)}
};


// debug checkpoint stub
dap.operate["--"]=function(){return}
