// tmpl 
// TODO:
// ･セッション情報をURLエンコードして入れているが、テストが適当なのでちゃんとテストする。
// ･closeを呼ばなくても良い仕組みを導入する。
// ･dataの順番が変わるとデータの出力順も変わるテンプレート関数.
// ･ページ仕様についてチェックする。
// ･エラー発生時はExceptionを投げるようにする。
// ･各パーサーの速度を計測する。
// ･データからのタイプ変更ができるようにする。
// ･データからselectにoptionを追加できる。
// ･データからradio,checkboxを追加できる。
// ･コメント内の<$a>を通す。
// ･scriptコメント内の<$a>を通す。
// ･plane内の<$a>を通す。
// ･textarea内の<$a>は通さない。
// ･エラーチェックをいろいろしっかりする。
// ･<$../d>という指定ができる。
// ･<$f/d>という指定ができる。
// ･<$f[1]/d>という指定ができる。
// ･ScriptDomを生成するD言語関数を生成する。
// ･scriptを実行する関数を生成する。
// ･block要素にvarを入れると空と判定してストリーミングを続行できるようにする。

import dom;
import html;
import cgi;
private import std.string;
	
class TmplDom 
{
	char[] name;
	TmplDom array[];				// 子ノード
	TmplDom[][char[]] arrayHash;	//
	TmplDom parent=null;
	char[][char[]] endFlg;//ブロックデータ出力が終了していることを示す。
	char[][char[]] attr;
	static ScriptDom[char[]] classHash;// ScriptDomのネイティブバージョンを入れるハッシュ
	ScriptDom script;//出力用スクリプト
	Dom page;//データ定義
	char[] outputstring;

	// 元テンプレートデータとページ仕様データからテンプレートdomを作成。
	this(char[] html,char[] pagexml,char[] name)
	{
		ScriptDom script;
		if(name in classHash){
			script = classHash[name].newScript();
		}else{
			TmplParser p = new TmplParser();
			Dom tmpl = p.parse(html);
//printf("%.*s\n",tmpl.toStringln());
			script = ScriptDom.tmplToScript(tmpl);
//printf("%.*s\n",script.toStringln());
		}
		// ページ仕様
		Dom page = null;
		try{
			page = Dom.parse(pagexml).getDocument();
		}catch(Exception e){}
		this(script,page,name);
	}
	this(ScriptDom script,Dom page,char[] name){
		this.script=script;
		this.page=page;
		this.name=name;
	}
	TmplDom newTmplDom(char[] html,char[] page, char[] name){
		return new TmplDom(html,page,name);
	}

	char[][char[]]attrChangeKey;
	char[][char[]]attrChangeValue;
	TmplDom setAttr(char[] name,char[] key,char[] value){
		if(name=="value")return this;
		attrChangeKey[name]=key;
		attrChangeValue[name]=value;
		return this;
	}
	TmplDom setValue(char[] name,char[] value){
		/*if(page!==null){
			Dom p=null;
			for(int i=0;i<page.array.length;i++){
				if(page.array[i].type==Dom.VAR){
					if(page.array[i].value==name){
						p=page.array[i];
						break;
					}
				}
			}
			if(p===null){
				char[] e="";
				while(dt.parent!==null){e=e~"/"~dt.name;dt=dt.parent;}
				printf("<font color=red>error not found '%.*s' in page spec.</font><br>\n",e~"/"~name);
			}
		}*/
		attr[name]=value;
		return this;
	}

	TmplDom addBlock(char[] name){
		Dom p=null;
		/*if(page!==null){
			for(int i=0;i<page.array.length;i++){
				if(page.array[i].type==Dom.BLOCK){
					if(page.array[i].value==name){
						p=page.array[i];
						break;
					}
				}
			}
			if(p===null){
				char[] e="";
			}
		}*/

		// 新しくTmplDomを作成
		TmplDom tmplDom = new TmplDom(script,p,name);
		array ~= tmplDom;
		arrayHash[tmplDom.name] ~= tmplDom;
		tmplDom.parent=this;

		return tmplDom;
	}
	TmplDom setDom(Dom dom){
		TmplDom tmpl=this;
		for(int i=0;i<dom.array.length;i++){
			Dom d = dom.array[i];
			if(d.type==Dom.VAR){
				tmpl.setValue(d.value,d.getAttr("v"));
			}else
			if(d.type==Dom.BLOCK){
				tmpl.addBlock(d.value).setDom(d);
			}
		}
		tmpl.close();
		return this;
	}

	void setSessionString(char[] str){
		script.sessionString=Dom.xmlEscape(str,0);
		script.hrefSessionString=Dom.xmlEscape(URI.encode(str),0);
	}
	char[] toString()
	{
		// 出力用スクリプト実行
		script.currentData=this;
		script.mode=script.OUTPUT;
		script.run();
		return outputstring;
	}
	void print(char[] s){
		TmplDom tmplDom=this;
		while(tmplDom.parent!==null){
//dbg("TmplDom.print parent="~std.string.toString(cast(int)tmplDom.parent)~"\n");
			tmplDom=tmplDom.parent;
		}
		tmplDom.outputstring ~= s;
	}
	void close(){}
}
//import std.file;
//void dbg(char[] str){
//	append("dbg.txt",cast(void[])str);
//}

 
class TmplParser 
{
	DefTag treeDefTags[];

	public this()
	{
		treeDefTags ~= new DefTag("<plaintext",">",DefTag.IGNORE|DefTag.HASATTR,
					   new DefTag("","",0));
		treeDefTags ~= new DefTag("<textarea",">",DefTag.IGNORE|DefTag.HASATTR,
					   new DefTag("</textarea",">",DefTag.IGNORE|DefTag.HASATTR));
		treeDefTags ~= new DefTag("</option",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("<option",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("</select",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("<select",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("<script",">",DefTag.IGNORE|DefTag.HASATTR|DefTag.SCRIPT,
					   new DefTag("</script",">",DefTag.IGNORE|DefTag.HASATTR));
		treeDefTags ~= new DefTag("<input","/>",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("<input",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("</form",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("<form",">",DefTag.IGNORE|DefTag.HASATTR);
		treeDefTags ~= new DefTag("<!--","-->",DefTag.HASCHILD);
		treeDefTags ~= new DefTag("</T.",">",DefTag.HASNAME|DefTag.HASATTR|DefTag.CHILD);
		treeDefTags ~= new DefTag("<T.",">",DefTag.HASNAME|DefTag.HASATTR|DefTag.CHILD);
		treeDefTags ~= new DefTag("<$",">",DefTag.HASNAME|DefTag.HASATTR|DefTag.CHILD);
		treeDefTags ~= new DefTag("<","/>",DefTag.HASNAME|DefTag.HASATTR|DefTag.HASCHILD);
		treeDefTags ~= new DefTag("<",">",DefTag.HASNAME|DefTag.HASATTR|DefTag.HASCHILD);
	}

	Dom parse(char[] str){
		HtmlParser htmlparser=new HtmlParser();
		Node[] nodes = htmlparser.parse(str,false,treeDefTags);
		int pos=0;
		TagNode tagnode;
		Node node;
		char[] text;
		Dom dom = new Dom(Dom.BLOCK,"model");
		Dom root = dom;
		Dom[] stack;
		char[] blockname="";
		char[] blockname0="";
		while(pos<nodes.length){
			node=nodes[pos];
//printf("%.*s\n",node.toString());
			if(cast(TagNode)node){
				tagnode=cast(TagNode)node;
//printf("%.*s\n",tagnode.name);
				if(tagnode.start=="<$"){
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					dom.add(newDom(Dom.VAR,tagnode.name));
					pos++;
					continue;
				}
				if(tolower(tagnode.start)=="<input"){
					if(tagnode.getAttr("name")==""){
						text ~= node.toString();
						pos++;
						continue;
					}
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					char[] type=tolower(tagnode.getAttr("type"));

					if(type == "checkbox"){
//printf("tagnode.getAttr(value) %.*s\n",tagnode.getAttr("value"));
						dom.add(newDom(Dom.BLOCK,tagnode.getAttr("name"))
							.setAttr("type","checkbox")
							.setAttr("v",tagnode.getAttr("value"))
							.add(tagnode.toDom())
						);
					}else
					if(type=="radio"){
						dom.add(newDom(Dom.BLOCK,tagnode.getAttr("name"))
							.setAttr("type","radio")
							.setAttr("v",tagnode.getAttr("value"))
							.add(tagnode.toDom())
						);
					}else{
						//text
						//submit
						//button
						//text
						//password
						//file
						//hidden
						//submit
						//reset
						//button
						//image
						dom.add(newDom(Dom.BLOCK,tagnode.getAttr("name"))
							.setAttr("type","input")
							.setAttr("v",tagnode.getAttr("value"))
							.add(tagnode.toDom())
						);
					}
//printf(tagnode.toString());
//printf(tagnode.toDom().toString());
					pos++;
					continue;
				}
				if(tolower(tagnode.start)=="<textarea"){
					if(tagnode.getAttr("name")==""){// 名前がなければselectと認識しない。
						text ~= node.toString();
						pos++;
						continue;
					}
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					Dom textareaDom=newDom(Dom.BLOCK,tagnode.getAttr("name"))
						.setAttr("type","textarea")
						.add(tagnode.toDom());
					pos++;
					while(pos<nodes.length){// textarea内ループ
						node=nodes[pos];
						if(cast(TagNode)node){
							tagnode=cast(TagNode)node;
							if(tolower(tagnode.start)=="</textarea"){
								textareaDom.add(tagnode.toDom());
								pos++;
								break;
							}
						}else{
							textareaDom.setAttr("v",node.toString());
						}
						pos++;
					}
//printf("textareaDom=%.*s\n",textareaDom.toString());
					dom.add(textareaDom);
					continue;
				}
				if(tolower(tagnode.start)=="<select"){
					if(tagnode.getAttr("name")==""){// 名前がなければselectと認識しない。
						text ~= node.toString();
						pos++;
						continue;
					}
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					Dom selectDom=newDom(Dom.BLOCK,tagnode.getAttr("name"))
						.setAttr("type","select")
						.add(tagnode.toDom());
					pos++;
					while(pos<nodes.length){// select内ループ
						node=nodes[pos];
//printf("select node=%.*s\n",node.toString());
						if(cast(TagNode)node){
							tagnode=cast(TagNode)node;
							if(text!="")selectDom.add(newDom(Dom.TEXT,text));
							text = "";
							if(tolower(tagnode.start)=="</select"){
								selectDom.add(tagnode.toDom());
								pos++;
								break;
							}else
							if(tolower(tagnode.start)=="<select"
							|| tolower(tagnode.start)=="<form"
							|| tolower(tagnode.start)=="</form"
							|| tolower(tagnode.start)=="<input"
							|| tolower(tagnode.start)=="<textarea"
							){
//printf("select break\n");

								break;
							}else
							if(tolower(tagnode.start)=="<option"){// option内ループ
								Dom optionDom = newDom(Dom.BLOCK,"option").add(tagnode.toDom());
								char[] value = tagnode.getAttr("value");
//printf("option value=%.*s\n",value);
								if(value != "" ){
									optionDom.setAttr("v",value);
								}
								pos++;
								while(pos<nodes.length){
//printf("option node=%.*s\n",node.toString());
									node=nodes[pos];
									if(cast(TagNode)node){
										tagnode=cast(TagNode)node;
										if(tolower(tagnode.start)=="</option"){
											if(value==""){
												value=text;
												optionDom.setAttr("v",value);
											}
											text ~= tagnode.toString();
											optionDom.add(newDom(Dom.TEXT,text));
											text = "";
											pos++;
											break;
										}
										if(tolower(tagnode.start)=="<option"
										|| tolower(tagnode.start)=="</select"
										|| tolower(tagnode.start)=="<select"
										|| tolower(tagnode.start)=="<form"
										|| tolower(tagnode.start)=="</form"
										|| tolower(tagnode.start)=="<input"
										|| tolower(tagnode.start)=="<textarea"
										){
											if(value==""){
												value=text;
												optionDom.setAttr("v",value);
											}
											optionDom.add(newDom(Dom.TEXT,text));
											text = "";
											break;
										}
										text ~= node.toString();
									}else{
										text ~= node.toString();
									}
									pos++;
								}
								selectDom.add(optionDom);
								pos--;
							}
						}else{
							text ~= node.toString();
						}
						pos++;
					}
//printf("selectDom=\n%.*s\n",selectDom.toStringln());
					dom.add(selectDom);
					continue;
				}
				if(tagnode.start=="<T."){
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					Dom ndom = newDom(Dom.BLOCK,tagnode.name);
					dom.add(ndom);
					stack ~= dom;
					dom=ndom;
					blockname=tagnode.name;
					pos++;
					continue;
				}
				if(tagnode.start=="</T."){
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					if(blockname==tagnode.name){
						dom = stack[stack.length-1];stack.length=stack.length-1;
						blockname=dom.value;
					}else{
						printf("error block tag %.*s blockname=%.*s\n",tagnode.toString());
					}
					pos++;
					continue;
				}
				if(tolower(tagnode.start)=="<form"){
//printf("form node=%.*s\n",node.toString());
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";

					Dom ndom = newDom(Dom.BLOCK,tagnode.getAttr("name"))
						.setAttr("type","form")
						.add(newDom(Dom.BLOCK,tagnode.getAttr("name"))
							.setAttr("type","formtag")
							.add(tagnode.toDom())
						);

					dom.add(ndom);
					stack ~= dom;
					dom=ndom;
					blockname0=tagnode.getAttr("name");
					blockname="_form_"~blockname0;
					pos++;
					continue;
				}
				if(tolower(tagnode.start)=="</form"){
					if(text!="")dom.add(newDom(Dom.TEXT,text));
					text = "";
					if(dom.getAttr("type")=="form"){
						dom.add(newDom(Dom.BLOCK,blockname0)
							.setAttr("type","formend")
							.add(tagnode.toDom())
						);
						dom = stack[stack.length-1];stack.length=stack.length-1;
						blockname=dom.value;
					}else{
						printf("error block </form> tag %.*s blockname=%.*s tag=%.*s\n",blockname,tagnode.toString());
					}
					pos++;
					continue;
				}
				if(tagnode.start=="<" && (tagnode.getAttr("href")!="") && (tolower(tagnode.name)=="a" || tolower(tagnode.name)=="area")){
					char[] href=tagnode.getAttr("href");
					if(href.length<7 || href[0..7]!="http://"){
						text ~= tagnode.start~tagnode.name;
						for(int i=0;i<tagnode.attr.length;i++){
							Attr attr = tagnode.attr[i];
							if(attr.name=="href"){
								text ~= attr.space0~attr.name~attr.space1;
								dom.add(newDom(Dom.TEXT,text));
								text=attr.space2;
								dom.add(newDom(Dom.VAR,"link").setAttr("href",attr.value).setAttr("type","link"));
								continue;
							}
							text ~= attr.toString();
						}
						text ~= tagnode.endspace;
						text ~= tagnode.end;
						pos++;
						continue;
					}
				}
			}
			text ~= node.toString();
			pos++;
		}
		if(text!="")dom.add(newDom(Dom.TEXT,text));
		return root;
	}
}
 
class ScriptDom:Dom 
{
	enum{OUTPUT,STREAMING};
	int mode;
	int pos;
	TmplDom stack[];
	int var[char[]];
	TmplDom currentData;
	char[] sessionString;
	char[] hrefSessionString;
	void print(char[] str){
		currentData.print(str);
	}
	ScriptDom newScript(){
		return new ScriptDom(Dom.BLOCK,"script");
	}
	this(byte type,char[] value){
		super(type,value);
		pos=0;
	}
	static ScriptDom domToScript(Dom dom)
	{
		ScriptDom script = new ScriptDom(dom.type,dom.value);
		foreach(char[]key,char[]value;dom.attr){
			script.setAttr(key,value);
		}
		for(int i=0;i<dom.array.length;i++){
			script.add(domToScript(dom.array[i]));
		}
		return script;
	}
	static ScriptDom tmplToScript(Dom tmpl)
	{
		ScriptDom script = new ScriptDom(Dom.BLOCK,"script");
		tmplToScript(tmpl,script,0);
		script.add(newDom(Dom.VAR,"end"));
		return script;
	}
	static int tmplToScript(Dom tmpl,ScriptDom script,int pos){
		for(int i=0;i<tmpl.array.length;i++){
			Dom d = tmpl.array[i];
			if(d.type==Dom.TEXT){
				if(d.value!="")
					script.add(newDom(Dom.VAR,"text").setAttr("v",d.value));pos++;
			}else
			if(d.type==Dom.VAR){
				if(d.getAttr("type")=="link"){
					script.add(newDom(Dom.VAR,"link").setAttr("v",d.value)
						.setAttr("href",d.getAttr("href"))
					);
					pos++;
				}else{
					script.add(newDom(Dom.VAR,"var").setAttr("v",d.value));
					pos++;
				}
			}else
			if(d.type==Dom.BLOCK){
				if(d.getAttr("type")=="input"){
					script.add(newDom(Dom.BLOCK,"input").setAttr("v",d.value)
						.add(d.get("tag"))
					);
					pos++;
				}else
				if(d.getAttr("type")=="radio"){
					script.add(newDom(Dom.BLOCK,"radio").setAttr("v",d.value)
						.add(d.get("tag"))
					);
					pos++;
				}else
				if(d.getAttr("type")=="checkbox"){
					script.add(newDom(Dom.BLOCK,"checkbox").setAttr("v",d.value)
						.add(d.get("tag"))
					);
					pos++;
				}else
				if(d.getAttr("type")=="textarea"){
					Dom textarea=newDom(Dom.BLOCK,"textarea").setAttr("v",d.value);
					for(int i=0;i<d.array.length;i++){
						if(d.array[i].value=="tag")textarea.add(d.array[i]);
						if(i==0)textarea.add(newDom(Dom.TEXT,d.getAttr("v")));
					}
					script.add(textarea);
					pos++;
				}else
				if(d.getAttr("type")=="select"){
					Dom select=newDom(Dom.BLOCK,"select").setAttr("v",d.value);
					for(int i=0;i<d.array.length;i++){
						select.add(d.array[i]);
					}
					script.add(select);
					pos++;
				}else
				if(d.getAttr("type")=="formtag"){
					script.add(newDom(Dom.BLOCK,"form").setAttr("v",d.value)
						.add(d.get("tag"))
					);
					pos++;
				}else
				if(d.getAttr("type")=="formend"){
					script.add(newDom(Dom.BLOCK,"form").setAttr("v",d.value)
						.add(d.get("tag"))
					);
					pos++;
				}else
				{
					char[] label=std.string.toString(pos);
					Dom for1=newDom(Dom.VAR,"for").setAttr("tmpl",d.value);
					script.add(for1);pos++;
					pos = tmplToScript(d,script,pos);
					script.add(newDom(Dom.VAR,"next").setAttr("goto",label));
					label=std.string.toString(pos);
					for1.setAttr("goto",label);
					pos++;
				}
			}
		}
		return pos;
	}
	void run(){
		loop:
		while(true){
//dbg("pos "~std.string.toString(pos));
			Dom s = array[pos];
//dbg("[["~std.string.toString(s.type)~","~s.toString()~"]]\n");
			switch(s.value){
			case "text":
				print(s.attr["v"]);
				break;
			case "var":
				if(!(s.attr["v"] in currentData.attr)){
					if(mode == OUTPUT){
						throw(new Exception("not found program data &lt;$"~s.attr["v"]~"&gt; line="~s.attr["line"]~" pos="~s.attr["pos"]));
					}
					return;
				}
				print(currentData.attr[s.attr["v"]]);
				break;
			case "input":
				if(!(s.attr["v"] in currentData.attr)){
					if(mode == OUTPUT){
						TagNode tagnode=newTagNode(s.array[0]);
						if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
							tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
						}
						print(tagnode.toString());
						break;
					}
					return;
				}
				TagNode tagnode=newTagNode(s.array[0]);
				if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
					tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
				}
				tagnode.setAttr("value",currentData.attr[s.attr["v"]]);
				print(tagnode.toString());
				break;
			case "radio":
				if(!(s.attr["v"] in currentData.attr)){
					if(mode == OUTPUT){
						TagNode tagnode=newTagNode(s.array[0]);
						if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
							tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
						}
						print(tagnode.toString());
						break;
					}
					return;
				}
				TagNode tagnode=newTagNode(s.array[0]);
				if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
					tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
				}
///printf("tagnode.getAttr(value)=%.*s currentData.attr[s.attr[v]]=%.*s",tagnode.getAttr("value"),currentData.attr[s.attr["v"]]);
				if(tagnode.getAttr("value")==currentData.attr[s.attr["v"]]){
					Attr attr = new Attr();
					attr.space0=" ";
					attr.name="checked";
					tagnode.setAttr(attr);
				}else{
					tagnode.removeAttr("checked");
				}
				print(tagnode.toString());
				break;
			case "checkbox":
				if(!(s.attr["v"] in currentData.attr)){
					if(mode == OUTPUT){
						TagNode tagnode=newTagNode(s.array[0]);
						if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
							tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
						}
						print(tagnode.toString());
						break;
					}
					return;
				}
				TagNode tagnode=newTagNode(s.array[0]);
				if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
					tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
				}
///printf("tagnode.getAttr(value)=%.*s currentData.attr[s.attr[v]]=%.*s",tagnode.getAttr("value"),currentData.attr[s.attr["v"]]);
				char[][] v = currentData.attr[s.attr["v"]].split(",");
				int i;
				for(i=0;i<v.length;i++){
					if(tagnode.getAttr("value")==v[i]){
						Attr attr = new Attr();
						attr.space0=" ";
						attr.name="checked";
						tagnode.setAttr(attr);
						print(tagnode.toString());
						break;
					}else{
						tagnode.removeAttr("checked");
					}
				}
				if(i==v.length){
					print(tagnode.toString());
				}
				break;
			case "textarea":
				if(!(s.attr["v"] in currentData.attr)){
					if(mode == OUTPUT){
						TagNode tagnode=newTagNode(s.array[0]);
						if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
							tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
						}
						print(tagnode.toString());
						if(s.array.length>1){
							int no=1;
							if(s.array[no].type==TEXT){
								no++;
								print(s.array[1].toString());
							}
							if(s.array.length>no){
								tagnode=newTagNode(s.array[no]);
								print(tagnode.toString());
							}
						}
						break;
					}
					return;
				}
				TagNode tagnode=newTagNode(s.array[0]);
				if(s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
					tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
				}
				print(tagnode.toString());
				print(currentData.attr[s.attr["v"]]);
				if(s.array.length>1){
					int no=1;
					if(s.array[no].type==TEXT)no++;
					if(s.array.length>no){
						tagnode=newTagNode(s.array[no]);
						print(tagnode.toString());
					}
				}
				break;
			case "select":
				if(!(s.attr["v"] in currentData.attr)){
					if(mode == OUTPUT){
						for(int i=0;i<s.array.length;i++){
							Dom d = s.array[i];
							if(d.value=="tag"){
								TagNode tagnode=newTagNode(d);
								if(i==0 && s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
									tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
								}
								print(tagnode.toString());
							}else
							if(d.value=="option"){
								TagNode tagnode=newTagNode(d.array[0]);
								print(tagnode.toString());
								print(d.array[1].value);
							}
						}
						break;
					}
					return;
				}
				for(int i=0;i<s.array.length;i++){
					Dom d = s.array[i];
					if(d.value=="tag"){
						TagNode tagnode=newTagNode(d);
						if(i==0 && s.attr["v"] in currentData.attrChangeKey){// アトリビュート変更設定時
							tagnode.setAttr(currentData.attrChangeKey[s.attr["v"]],currentData.attrChangeValue[s.attr["v"]]);
						}
						print(tagnode.toString());
					}else
					if(d.value=="option"){
						TagNode tagnode=newTagNode(d.array[0]);
						char[][] v = currentData.attr[s.attr["v"]].split(",");
						int j;
						for(j=0;j<v.length;j++){
							if(d.getAttr("v")==v[j]){
								Attr attr = new Attr();
								attr.space0=" ";
								attr.name="selected";
								tagnode.setAttr(attr);
								print(tagnode.toString());
								break;
							}
						}
						if(j==v.length){
							tagnode.removeAttr("selected");
							print(tagnode.toString());
						}

						print(d.array[1].value);
					}
				}
				break;
			case "form":
				TagNode tagnode=newTagNode(s.array[0]);
				if(tolower(tagnode.start)=="<form" && s.attr["v"] in currentData.parent.attrChangeKey){// アトリビュート変更設定時
					tagnode.setAttr(currentData.parent.attrChangeKey[s.attr["v"]],currentData.parent.attrChangeValue[s.attr["v"]]);
				}
				print(tagnode.toString());
				if(tolower(tagnode.start)=="<form" && sessionString!="")
					print("<input type=\"hidden\" name=\"__session\" value=\""~sessionString~"\"/>");
				break;
			case "for":
				if(currentData.arrayHash[s.attr["tmpl"]].length==0){
					if(mode==OUTPUT){
						if(s.attr["goto"]=="")throw(new Exception("script dom for error "~s.toString()));
						pos=atoi(s.attr["goto"]);
//dbg("goto pos="~std.string.toString(pos)~"\n");
						break;
					}
					return;
				}
				s.attr["tmpl"];
				stack ~= currentData;
				var["i"~std.string.toString(pos)]=0;
				currentData = stack[stack.length-1].arrayHash[s.attr["tmpl"]][var["i"~std.string.toString(pos)]];
				break;
			case "next":
				if(stack[stack.length-1].arrayHash[currentData.name].length >
					var["i"~s.attr["goto"]]+1 ){
					var["i"~s.attr["goto"]]++;
					currentData = stack[stack.length-1].arrayHash[currentData.name][var["i"~s.attr["goto"]]];
					if(s.attr["goto"]=="")throw(new Exception("script dom error "~s.toString()));
					pos=atoi(s.attr["goto"]);
//dbg("goto pos="~std.string.toString(pos)~"\n");
				}else{
					if(mode==OUTPUT||stack[stack.length-1].endFlg[currentData.name]=="end"){
						currentData = stack[stack.length-1];stack.length=stack.length-1;
					}else{
						return;//ここで判断せずに、親のsetValueもしくは、addBlockが呼ばれたら実行する。
					}
					var["i"~s.attr["goto"]]++;
				}
				break;
			case "link":
				char[] href=s.attr["href"];
				if(sessionString=="")
					print(s.attr["href"]);
				else
				if(find(href,"?")==-1)
					print(s.attr["href"]~"?__session="~hrefSessionString);
				else
					print(s.attr["href"]~"&__session="~hrefSessionString);
				break;
			case "end":
				break loop;
			default:
				throw(new Exception("Script.run error s.value error"~s.value));
				break;
			}
			pos++;
		}
	}
	void close(TmplDom thisData)
	{
		if(stack.length==0)return;
		if(thisData==stack[stack.length-1]){
			char[] name = currentData.name;
			currentData = stack[stack.length-1];stack.length=stack.length-1;
			currentData.endFlg[name]="end";
			pos++;
		}else{
			if(stack[stack.length-1].arrayHash[currentData.name].length >
				var["i"~array[pos].attr["goto"]]+1 ){
				var["i"~array[pos].attr["goto"]]++;
				currentData = stack[stack.length-1].arrayHash[currentData.name][var["i"~array[pos].attr["goto"]]];
				pos=atoi(array[pos].attr["goto"]);
			}
		}
		run();
	}

}
 
class StreamingTmplDom:TmplDom 
{
	bool test=false;
version(Windows){
	import std.c.windows.windows;
	void sleep(long msec){
	//	Sleep(msec);
	}
}else{
	void sleep(long msec){}
}
	this(char[] html,char[] page, char[] name){
		super(html,page,name);
		script.currentData = this;
		script.mode=script.STREAMING;
	}
	this(char[] html,char[] page, char[] name,bit test){
		this.test=test;
		super(html,page,name);
		script.currentData = this;
		script.mode=script.STREAMING;
	}
	this(ScriptDom script,Dom page,char[] name){
		super(script,page,name);
		script.currentData = this;
		script.mode=script.STREAMING;
	}

	this(ScriptDom script,Dom page,char[] name,int n){
		this(script,page,name);
	}
	TmplDom newTmplDom(char[] html,char[] page, char[] name){
		StreamingTmplDom t = new StreamingTmplDom(html,page,name,test);
		return t;
	}
	TmplDom setValue(char[] name,char[] value){
	//printf("setValue(%.*s,%.*s)\n",name,value);
		super.setValue(name,value);
		script.run();
		sleep(500);
		return this;
	}
	TmplDom addBlock(char[] name){
	//printf("addBlock(%.*s)\n",name);
		Dom p=null;
		/*if(page!==null){
			for(int i=0;i<page.array.length;i++){
				if(page.array[i].type==Dom.BLOCK){
					if(page.array[i].value==name){
						p=page.array[i];
						break;
					}
				}
			}
			if(p===null){
				char[] e="";
				TmplDom dt=this;
				while(dt.parent!==null){e=e~"/"~dt.name;dt=dt.parent;}
				printf("<font color=red>error not found '%.*s' in page spec.</font><br>\n",e~"/"~name);
			}
		}*/
		// 新しくTmplDomを作成
		StreamingTmplDom tmplDom = new StreamingTmplDom(script,p,name,2);
		array ~= tmplDom;
		arrayHash[tmplDom.name] ~= tmplDom;
		tmplDom.parent=this;

		script.run();
		sleep(500);
		return tmplDom;
	}
	void print(char[] s){
		StreamingTmplDom tmplDom=this;
		while(tmplDom.parent!==null){
			tmplDom=cast(StreamingTmplDom)tmplDom.parent;
		}
		if(tmplDom.test){
			tmplDom.outputstring ~= s;
		}else{
			printf("%.*s",s);
		}
	}
	char[] toString(){
		script.currentData=this;
		script.mode=script.OUTPUT;
		script.run();
		StreamingTmplDom tmplDom=this;
		while(tmplDom.parent!==null){
			tmplDom=cast(StreamingTmplDom)tmplDom.parent;
		}
		if(test){
			return outputstring;
		}else{
			return "";
		}
	}
	void close(){
		script.close(this);
	}
}

 
class test:ScriptDom{ 
	static this(){
		TmplDom.classHash["test"]=new test(Dom.BLOCK,"script");
	}
	ScriptDom newScript(){
		return new test(Dom.BLOCK,"script");
	}
	this(byte type,char[] value){
		super(type,value);
		pos=0;
	}
	void run(){
		while(true){
			switch(pos){
			case 0:print("<html>");pos++;
			case 1:
				if(!("dt" in currentData.attr)){
					if(mode == OUTPUT){
					throw(new Exception("not found program data &lt;$dt&gt; line=\"\" pos=\"\""));
					}
					return;
				}
				print(currentData.attr["dt"]);
				pos++;
			case 2:print("</html>");pos++;
			case 3:return;
			}
			pos++;
		}
	}
}
 
version(TMPL_TEST){ 
	
	void main() 
	{
		TmplDom tmpl;
		TmplDom tmpls[];
		tmpls ~= new TmplDom("","","");
		tmpl = new StreamingTmplDom("","","");
		(cast(StreamingTmplDom)tmpl).test=true;
		tmpls ~= tmpl;
		for(int i=0;i<tmpls.length;i++){
		printf("test %d\n",i);
			TmplDom t=tmpls[i];

			// formにセッション文字列を入れる。
			tmpl = t.newTmplDom("<html><form name=f>aaa</form></html>","","");
			tmpl.setSessionString("abcd");
			tmpl.addBlock("f");
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=\"hidden\" name=\"__session\" value=\"abcd\"/>aaa</form></html>");

			// リンクにセッション文字列を入れる。
			tmpl = t.newTmplDom("<html><a href=abc>aaa</a></html>","","");
			tmpl.setSessionString("abcd");
			tmpl.close();
			assert(tmpl.toString()=="<html><a href=abc?__session=abcd>aaa</a></html>");

			// リンクに?がある場合でセッション文字列を入れる。
			tmpl = t.newTmplDom("<html><a href=abc?abc>aaa</a></html>","","");
			tmpl.setSessionString("abcd");
			tmpl.close();
			assert(tmpl.toString()=="<html><a href=abc?abc&__session=abcd>aaa</a></html>");

			// リンクにhttp://がある場合セッション文字列を入れない。
			tmpl = t.newTmplDom("<html><a href=http://aaa/abc>aaa</a></html>","","");
			tmpl.setSessionString("abcd");
			tmpl.close();
			assert(tmpl.toString()=="<html><a href=http://aaa/abc>aaa</a></html>");

			// リンクにhttp://がある場合に?があってもセッション文字列を入れない。
			tmpl = t.newTmplDom("<html><a href=http://aaa/abc?a=1>aaa</a></html>","","");
			tmpl.setSessionString("abcd");
			tmpl.close();
			assert(tmpl.toString()=="<html><a href=http://aaa/abc?a=1>aaa</a></html>");

			// セッション文字列に+や"があった場合
			tmpl = t.newTmplDom("<html><a href=a>aaa</a></html>","","");
			tmpl.setSessionString("a+\"");
			tmpl.close();
			assert(tmpl.toString()=="<html><a href=a?__session=a%2B&quot;>aaa</a></html>");



			// タグのないデータ
			tmpl = t.newTmplDom("<html></html>","","");
			assert(tmpl.toString()=="<html></html>");

			// データの設定を行うとき
			tmpl = t.newTmplDom("<html><$dt></html>","","");
			tmpl.setValue("dt","data");
			tmpl.close();
			assert(tmpl.toString()=="<html>data</html>");

			// データの設定をxmlから行うとき
			tmpl = t.newTmplDom("<html><$dt></html>","","");
			tmpl.setDom(Dom.parse("<model><dt v=\"test\"/></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html>test</html>");

			// ブロック要素があるとき
			tmpl = t.newTmplDom("<html><T.b>b:<$dt><br></T.b></html>","","");
			tmpl.setDom(Dom.parse("<model><b><dt v=\"test\"/></b></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html>b:test<br></html>");

			// ブロック要素が２つのとき
			tmpl = t.newTmplDom("<html><T.b>b:<$dt><br></T.b></html>","","");
			tmpl.setDom(Dom.parse("<model><b><dt v=\"test\"/></b><b><dt v=\"test2\"/></b></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html>b:test<br>b:test2<br></html>");

			// input text
			tmpl = t.newTmplDom("<html><form name=f><input type=text name=a></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"test\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=text name=a value=\"test\"></form></html>");

			// radio 値１設定
			tmpl = t.newTmplDom("<html><form name=f><input type=radio name=a value=1><input type=radio name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=radio name=a value=1 checked><input type=radio name=a value=2></form></html>");

			// radio 値２設定
			tmpl = t.newTmplDom("<html><form name=f><input type=radio name=a value=1><input type=radio name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=radio name=a value=1><input type=radio name=a value=2 checked></form></html>");

			// checkbox
			tmpl = t.newTmplDom("<html><form name=f><input type=checkbox name=a value=1><input type=checkbox name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=checkbox name=a value=1 checked><input type=checkbox name=a value=2></form></html>");

			// checkbox
			tmpl = t.newTmplDom("<html><form name=f><input type=checkbox name=a value=1><input type=checkbox name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=checkbox name=a value=1><input type=checkbox name=a value=2 checked></form></html>");

			// checkbox
			tmpl = t.newTmplDom("<html><form name=f><input type=checkbox name=a value=1><input type=checkbox name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1,2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=checkbox name=a value=1 checked><input type=checkbox name=a value=2 checked></form></html>");

			// textarea
			tmpl = t.newTmplDom("<html><form name=f><textarea name=a ></textarea></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1,2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><textarea name=a >1,2</textarea></form></html>");

			// textarea 改行コード入り
			tmpl = t.newTmplDom("<html><form name=f><textarea name=a ></textarea></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1&#10;2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><textarea name=a >1\n2</textarea></form></html>");

			// textarea 終わらないtextarea
			tmpl = t.newTmplDom("<html><form name=f><textarea name=a></body></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"aaa\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><textarea name=a>aaa");

			// textarea タグで終わる。
			tmpl = t.newTmplDom("<html><form name=f><textarea name=a>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"aaa\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><textarea name=a>aaa");

			// textarea form外にあるtextarea
			tmpl = t.newTmplDom("<html><textarea name=a>","","");
			tmpl.setDom(Dom.parse("<model><a v=\"aaa\"/></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><textarea name=a>aaa");

			// select 値１設定
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option value=1>a</option><option value=2>b</option></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option value=1 selected>a</option><option value=2>b</option></select></form></html>");

			// select 値２設定
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option value=1>a</option><option value=2>b</option></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option value=1>a</option><option value=2 selected>b</option></select></form></html>");

			// select <option>タグのvalue省略
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2</option></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2</option></select></form></html>");

			// select <option>タグの</option>タグ省略
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1<option>2</select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1<option selected>2</select></form></html>");

			// select inputタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2</option><input></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2</option><input></form></html>");

			// select textareaタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2</option><textarea></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2</option><textarea></form></html>");

			// select selectタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2</option><select></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2</option><select></select></form></html>");

			// select /formタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2</option></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2</option></form></html>");

			// select formタグ終了
			tmpl = t.newTmplDom("<html><select name=a><option>1</option><option>2</option><form name=f></form></html>","","");
			tmpl.setDom(Dom.parse("<model><a v=\"2\"/><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><select name=a><option>1</option><option selected>2</option><form name=f></form></html>");


			// option inputタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2<input></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2<input></form></html>");

			// option textareaタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2<textarea></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2<textarea></form></html>");

			// option selectタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2<select></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2<select></select></form></html>");

			// option /formタグ終了
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option>1</option><option>2</form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option>1</option><option selected>2</form></html>");

			// option formタグ終了
			tmpl = t.newTmplDom("<html><select name=a><option>1</option><option>2<form name=f></form></html>","","");
			tmpl.setDom(Dom.parse("<model><a v=\"2\"/><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><select name=a><option>1</option><option selected>2<form name=f></form></html>");

			// radio checkedはずし
			tmpl = t.newTmplDom("<html><form name=f><input type=radio name=a value=1 checked><input type=radio name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=radio name=a value=1><input type=radio name=a value=2 checked></form></html>");

			// checkbox checkedはずし
			tmpl = t.newTmplDom("<html><form name=f><input type=checkbox name=a value=1 checked><input type=checkbox name=a value=2></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=checkbox name=a value=1><input type=checkbox name=a value=2 checked></form></html>");
			// select selectedはずし
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option value=1>a</option><option value=2 selected>b</option></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option value=1 selected>a</option><option value=2>b</option></select></form></html>");
			// select 複数値設定
			tmpl = t.newTmplDom("<html><form name=f><select name=a><option value=1>a</option><option value=2>b</option></select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"1,2\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a><option value=1 selected>a</option><option value=2 selected>b</option></select></form></html>");


			// input text 空白
			tmpl = t.newTmplDom("<html><form name=f><input name=a value=\"1\"></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input name=a value=\"\"></form></html>");

			// textarea 空白
			tmpl = t.newTmplDom("<html><form name=f><textarea name=a ></textarea></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f><a v=\"\"/></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><textarea name=a ></textarea></form></html>");

			// ブロック要素が空のとき
			tmpl = t.newTmplDom("<html><T.b>b:<$dt><br></T.b></html>","","");
			tmpl.close();
			assert(tmpl.toString()=="<html></html>");

			// text フォーム項目に値を設定しないときは表示する。
			tmpl = t.newTmplDom("<html><form name=f><input type=text name=a value=abc></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=text name=a value=abc></form></html>");

			// radio フォーム項目に値を設定しないときは表示する。
			tmpl = t.newTmplDom("<html><form name=f><input type=radio name=a value=abc checked></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=radio name=a value=abc checked></form></html>");

			// checkbox フォーム項目に値を設定しないときは表示する。
			tmpl = t.newTmplDom("<html><form name=f><input type=checkbox name=a value=abc checked></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><input type=checkbox name=a value=abc checked></form></html>");

			// textarea フォーム項目に値を設定しないときは表示する。
			tmpl = t.newTmplDom("<html><form name=f><textarea name=a >data</textarea></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><textarea name=a >data</textarea></form></html>");

			// select フォーム項目に値を設定しないときは表示する。
			tmpl = t.newTmplDom("<html><form name=f><select name=a ><option>a<option selected>b</select></form></html>","","");
			tmpl.setDom(Dom.parse("<model><f></f></model>").getDocument());
			tmpl.close();
			assert(tmpl.toString()=="<html><form name=f><select name=a ><option>a<option selected>b</select></form></html>");

			// htmlに%が含まれているとき。
			tmpl = t.newTmplDom("<html><table width=100%><form name=f><input type=hidden value=new name=cmd><input type=text name=path><input type=text name=comment><input type=submit value=new></form></html>","","");
			tmpl.addBlock("f");
			assert(tmpl.toString()=="<html><table width=100%><form name=f><input type=hidden value=new name=cmd><input type=text name=path><input type=text name=comment><input type=submit value=new></form></html>");

			// input アトリビュート変更
			tmpl = t.newTmplDom("<html><form name=f><input type=text name=name></form></html>","","");
			TmplDom f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");//アトリビュート変更
			f.setValue("name","gagaga");
			assert(tmpl.toString()=="<html><form name=f><input type=text name=name1 value=\"gagaga\"></form></html>");

			// input アトリビュート変更
			tmpl = t.newTmplDom("<html><form name=f><input type=text name=name></form></html>","","");
			f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");//アトリビュート変更
			assert(tmpl.toString()=="<html><form name=f><input type=text name=name1></form></html>");

			// radio アトリビュート変更これは、全radioに変更がかかる。
			tmpl = t.newTmplDom("<html><form name=f><input type=radio value=1 name=name><input type=radio value=2 name=name></form></html>","","");
			f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");
			assert(tmpl.toString()=="<html><form name=f><input type=radio value=1 name=name1><input type=radio value=2 name=name1></form></html>");

			// checkbox アトリビュート変更これは、全checkboxに変更がかかる。
			tmpl = t.newTmplDom("<html><form name=f><input type=checkbox value=1 name=name><input type=checkbox value=2 name=name></form></html>","","");
			f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");
			assert(tmpl.toString()=="<html><form name=f><input type=checkbox value=1 name=name1><input type=checkbox value=2 name=name1></form></html>");

			// textarea アトリビュート変更
			tmpl = t.newTmplDom("<html><form name=f><textarea name=name></textarea></form></html>","","");
			f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");
			assert(tmpl.toString()=="<html><form name=f><textarea name=name1></textarea></form></html>");

			// select アトリビュート変更
			tmpl = t.newTmplDom("<html><form name=f><select name=name><option>a<option>b</select></form></html>","","");
			f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");
			assert(tmpl.toString()=="<html><form name=f><select name=name1><option>a<option>b</select></form></html>");

			// select アトリビュート変更
			tmpl = t.newTmplDom("<html><form name=f><select name=name><option>a<option>b</select></form></html>","","");
			tmpl.setAttr("f","method","GET");
			f = tmpl.addBlock("f");
			f.setAttr("name","name","name1");
			assert(tmpl.toString()=="<html><form name=f method=\"GET\"><select name=name1><option>a<option>b</select></form></html>");


			// リンクがあるとき
			tmpl = t.newTmplDom("<html><a href=abc>aaa</a></html>","","");
			tmpl.close();
			assert(tmpl.toString()=="<html><a href=abc>aaa</a></html>");

			// スタティックリンクされた、ScriptDomクラスがあるときデータの設定を行うとき
			tmpl = t.newTmplDom("<html><$dt></html>","","test");
			tmpl.setValue("dt","data");
			tmpl.close();
			assert(tmpl.toString()=="<html>data</html>");

			// クローズしたときに、dtが定義されていなければ、エラーが出てほしい
			try{
				tmpl = t.newTmplDom("<html><$dt></html>","","");
				tmpl.close();
				printf("%.*s\n",tmpl.toString());
				assert(false);
			}catch(Exception e){
				printf("%.*s\n",e.toString());
			}
			if(0){
				// form名前のないform
				tmpl = t.newTmplDom("<html><form>aaa</form></html>","","");
				tmpl.close();
				printf("%.*s\n",tmpl.toString());
				assert(tmpl.toString()=="<html><form>aaa</form></html>");

				// クローズすれば、エラーが出てほしい。否、溜め込んで最後のtoStringでエラーだな。
				tmpl = new StreamingTmplDom("<html><$dt></html>","","");
				tmpl.close();
				assert(tmpl.toString()=="<html></html>");
			}
		}
		printf("ok\n");
	}
 
} 
   
