// TODO:
// ページ仕様エディタの入力文字列チェックをしっかりしよう。XSS対策ができてない。
// ページ仕様エディタ本体の機能をちゃんとしたものにしよう。
// 無意味かもしれないけど、そこまで作ったらUPしよう。
// 変更点も書こう。サンプルURLも公開しよう。
// アクセスログとクリック履歴とりたいけど後だ。 
// クロスコンパイラのfreebsd版最新gdcをビルドしよう。これは後だな。とりあえず、無いものを作るのを優先しよう。 
// xreaのクロスコンパイラも再挑戦しよう。これも後だな。 
// テンプレートからページ仕様を生成する機能を作ろう。 
// プログラム出力から動的にページ仕様が生成されていく仕組みを考えよう。
import cgi;
import dom;
import tmpl;
import binload;
private import std.date;

extern(C) int exit(int status);
class NotFoundException:Exception
{
	this(char[]s){super(s);}
}
version(Windows){
extern(Windows) export bool SetEnvironmentVariableA(
  char* lpName,
  char* lpValue);
}
version(GNU){
//	version(freebsd){
		struct struct_stat {
			uint st_dev;
			uint st_ino;
			ushort st_mode;
			ushort st_nlink;
			uint st_uid;
			uint st_gid;
			uint st_rdev;

			int st_atime;
			int st_atime_nsec;
			int st_mtime;
			int st_mtime_nsec;
			int st_ctime;
			int st_ctime_nsec;
			long st_size;
			long st_blocks;
			uint st_blksize;
			uint st_flags;
			uint st_gen;
			int st_lspare;
			long st_qspare[2];
		}
//	}
/*	version(Windows){
		struct struct_stat
		{
		  uint st_dev;
		  ulong st_ino;
		  uint st_mode;
		  ushort st_nlink;
		  uint st_uid;
		  uint st_gid;
		  uint st_rdev;
		  long st_size;
		  int st_atime;
          int st_atime_nsec;
		  int st_mtime;
		  int st_mtime_nsec;
		  int st_ctime;
		  int st_ctime_nsec;
		  int st_blksize;
		  long st_blocks;
		  int st_spare4[2];
		}
	}
*/
}else
version(Windows){
	struct struct_stat
	{
		short st_dev;
		short st_ino;
		ushort st_mode;
		short st_nlink;
		short st_uid;
		short st_gid;
		short st_rdev;
		short dummy;
		int st_size;
		int st_atime;// 最新アクセス時刻
		int st_mtime;// 最新変更時刻
		int st_ctime;// ファイル作成時刻

	}
}
extern(C) int stat(char *path, struct_stat *buf);
extern(C) char *ctime(int *timer);
int getFileTime(char[] filename){
	struct_stat fstat;
	if ( stat(toStringz(filename), &fstat) != 0){
		return 0;
	}
	return cast(int)fstat.st_mtime;
}
class Sample{
	static CGI cgi;
	this(){
	}
	CGI getCGI(){if(cgi===null)cgi = new CGI();return cgi;}
	static char[] getHeader(){
		return  "Content-type: text/html; charset=Shift_JIS\n"
				"Pragma: no-cache\n"
				"Cache-Control: no-cache\n"
				"Expires: "~getExpires()~"\n";
	}
	private import std.date;
	private import std.md5;
	private import std.stream;
	private import std.base64;
	static char[] getSessionString(CGI cgi){
		if("__session" in cgi.reqs){
			return cgi.reqs["__session"];
		}else{
			return createSessionString(cgi);
		}
	}
	static char[] createSessionString(CGI cgi){
		char[] ip=Env["REMOTE_ADDR"];
		char[] url=Env["SCRIPT_URL"];
		ip=ip.replace(".","");
		long t = getUTCtime();
		char[] str=ip~url~std.string.toString(t);
		char[16] digest;
		sum(cast(ubyte[16])digest,cast(void[])str);
		MemoryStream m=new MemoryStream();
		for(int i=0;i<16;i++)
			m.printf("%c",cast(int)digest[i]);
		m.close();
		m.printf("%0x%0x",t,cast(int)atoi(ip));
		str=std.base64.encode(cast(char[])m.data);
		str=str.replace("=","").replace("/","+a");
		char[] b;
		int len=str.length;
		b.length=len;
		int rand=5431;
		for(int i=0;i<len;i++){
			rand=(rand*2341+431)% str.length;
			b[i]=str[rand];
			str=str[0..rand]~str[rand+1..str.length];
		}
		return b;
	}
	static char[][char[]] readSession(CGI cgi)
	{
		char[] sessionString = Sample.getSessionString(cgi);
		char[][char[]] session;
		if(sessionString=="")return session;
		try{
			char[] data=cast(char[])std.file.read("session/"~sessionString);
			Dom dom = BinUnpack.unpack(data).getDocument();
			return dom.attr;
		}catch(Exception e){
		}
		return session;
	}

	static bool writeSession(CGI cgi,char[][char[]]session)
	{
		char[] sessionString = Sample.getSessionString(cgi);
		if(sessionString=="")return false;
		Dom dom = new Dom(Dom.VAR,"session");
		foreach(char[] key,char[] value;session){
			dom.setAttr(key,value);
		}
		char[] data=BinPack.pack(dom);
		write("session/"~sessionString,cast(void[])data);
		return true;
	}

	static TmplDom readTmplDom(char[] command){
		int htmltime = getFileTime(command~".html");//time 関数は，1970年1月1日からの秒数
		int bintime = getFileTime(command~".bin");//
//printf("%.*s\n",Sample.getHeader());
//printf("%.*s\n",command);
//printf("htmltime=%s<br>\n",ctime(&htmltime));
//printf("bintime=%s<br>\n",ctime(&bintime));

		if(bintime<htmltime){
			char[] html = read(command~".html");
			TmplDom tmpl = new TmplDom(html,null,command);
			tmpl.setSessionString(getSessionString(cgi));

			write(command~".bin",cast(void[])BinPack.pack(tmpl.script));
			return tmpl;
		}
//printf("cache<br>\n");
		char[] bin = read(command~".bin");
		Dom dom = BinUnpack.unpack(bin);
		dom=dom.getDocument();
		ScriptDom script = ScriptDom.domToScript(dom);
		TmplDom tmpl = new TmplDom(script,null,command);
		tmpl.setSessionString(getSessionString(cgi));
		return tmpl;
	}
	static char[] getFilename(){
		char[] pathinfo = Env["PATH_INFO"];
		pathinfo = pathinfo.replace("..","");
		if(pathinfo=="")pathinfo="/index.html";
		if(pathinfo=="/")pathinfo="/index.html";
		//.htaccessで名前変換して、.htmlを.tmplにした場合
		if(pathinfo.length>=5 && pathinfo[pathinfo.length-5..pathinfo.length]==".tmpl"){
			pathinfo[pathinfo.length-5..pathinfo.length]=".html";
		}
		//.htaccessで名前変換している場合に、自cgiを普通に使った場合
		if(pathinfo.length>=1+cgi.self_name.length && pathinfo[0..6]=="/"~cgi.self_name){
			pathinfo=pathinfo[1+cgi.self_name.length..pathinfo.length];
		}
		if(pathinfo.length>1 && pathinfo[0]=='/'){
			pathinfo = pathinfo[1..pathinfo.length];
		}
		return pathinfo;
	}
	void setCommandLineOption(char[] exename,char[][char[]] param){
		if(Env["REQUEST_METHOD"]==""){
			if(param["REQUEST_METHOD"]==""){
				param["REQUEST_METHOD"]="GET";
			}
version(Windows){
			char*[] envs;
			foreach(char[] key,char[] value;param){
				SetEnvironmentVariableA(key.toStringz(),value.toStringz());
			}
}
		}

	}

	static char[] getCommand(){
		char[] filename = getFilename();
		if(filename.length>5 && filename[filename.length-5..filename.length]==".html"){
			return filename[0..filename.length-5];
		}
		return "";
	}

	void function(CGI cgi,char[] command) f[char[]];
	void setFunction(char[] name, void function(CGI cgi,char[] filename) func){
		f[name]=func;
	}
	void dispatch()
	{
		char[]  command = getCommand();
		char[] filename = getFilename();
		try{
			if(f[command]!==null){
				f[command](cgi,filename);
			}else{
				print_default(cgi,filename);
			}
		}catch(NotFoundException o){
		}catch(Exception o){
			printf("%.*s\n%.*s",Sample.getHeader(),o.toString());
		}
		
	}
	private import std.regexp;
	private import std.file;
	version(GNU){
		private import std.file;
		static bool exists(char[] filename){
			try{
				(new File(filename,FileMode.In)).close();
			}catch(Exception e){
				return false;
			}
			return true;
		}
	}
	static char[] read(char[] filename){
		if(!exists(filename)){
			printf("%.*s",Sample.getHeader());
		    printf("Status: 404 Not Found\n\n");
		    printf("filename=%.*s\n",filename);
			try{assert(false);}catch(Object o){throw(new NotFoundException("file not found. "~line(o)));}
		}
		return cast(char[])std.file.read(filename);
	}
	static char[] line(Object o){
		char[][] s = (new RegExp(`\(.*\)`,"")).match(o.toString());
		if(s.length>0)
			return s[0];
		return o.toString();
	}
}
private import std.regexp;
void main()
{
	Sample sample = new Sample();
	char[][char[]]opt;
	opt["REQUEST_METHOD"]="GET";
	opt["PATH_INFO"]="/new.html";
	opt["QUERY_STRING"]="__session=ml%2BlgRmSzpZYm3WDWb67N9TZYacfVgOdV7YV";
	opt["REQUEST_URI"]="http:/localhost/pageedit.cgi"~opt["PATH_INFO"]~"?"~opt["QUERY_STRING"];
	sample.setCommandLineOption("pageedit",opt);

    CGI cgi = sample.getCGI();
	sample.setFunction("index",&print_index);
	sample.setFunction("new",&print_new);
	sample.setFunction("edit",&print_edit);
	sample.setFunction("editend",&print_editend);
	sample.setFunction("list",&print_list);
	sample.setFunction("detaillist",&print_detaillist);
	sample.setFunction("detail",&print_detail);
	sample.dispatch();
}
//private import std.file;
//void dbg(char[] str){
//	append("dbg.txt",cast(void[])str);
//}
void print_default(CGI cgi,char[] filename)
{
	if("__session" in cgi.reqs){
		if(filename.length>5 && filename[filename.length-5..filename.length]==".html"
		){
			char[] command=Sample.getCommand();
			TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/"~command);
			printf("%.*s\n",Sample.getHeader());
			printf("%.*s",tmpl.toString().replace("\r",""));
			return;
		}
	}
	char[] html = Sample.read("tmpl_pageedit/"~filename);
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",html.replace("\r",""));
}

void print_index(CGI cgi,char[] filename){
	char[] command=Sample.getCommand();
	TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/"~command);
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",tmpl.toString().replace("\r",""));
	return;
}
int search(char[] str,RegExp reg){
	return reg.search(str);
}
void print_new(CGI cgi,char[] filename){
	char[] command = Sample.getCommand();
	if(cgi.reqs["cmd"]=="new"){
		char[][] error;
		if(cgi.reqs["path"]==""){
			error~="errorPathNull";
		}else
		if(cgi.reqs["path"].search(new RegExp(`\.\.`,""))!=-1){
			error~="errorPathError";
		}
		if(error.length!=0){
			TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/new");
			for(int i=0;i<error.length;i++)
				tmpl.addBlock(error[i]);
			TmplDom f = tmpl.addBlock("f");
			f.setValue("cmd","new");
			f.setValue("path",cgi.reqs["path"]);
			f.setValue("comment",cgi.reqs["comment"]);
			printf("%.*s\n",Sample.getHeader());
			printf("%.*s\n",tmpl.toString().replace("\r",""));
		}else{
			char[][char[]] session;
			session["new_path"]=cgi.reqs["path"];
			session["new_comment"]=cgi.reqs["comment"];
			session["address"]="<a href=new.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様新規作成</a>";
			Sample.writeSession(cgi,session);
			print_edit(cgi,"edit.html");
		}
	}else{
		TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/new");
		TmplDom f = tmpl.addBlock("f");
		if(cgi.reqs["cmd"]=="load"){
			char[][char[]] session = Sample.readSession(cgi);
			f.setValue("path",session["new_path"]);
			f.setValue("comment",session["new_comment"]);
		}
		printf("%.*s\n",Sample.getHeader());
		printf("%.*s\n",tmpl.toString().replace("\r",""));
	}
}

void print_edit(CGI cgi,char[] filename){

	int i=0;
	int mm=0;
	char[][char[]] data;
	char[]mms="0";
	int space=0;
	char[][char[]] session=Sample.readSession(cgi);
	session["address"]=session["address"].replace(" > <a href=edit.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様編集</a>","");
	if(cgi.reqs["cmd"]=="new"){
		cgi.reqs["path"]=cgi.reqs["path"];
		cgi.reqs["name0"]=cgi.reqs["path"];
		cgi.reqs["comment0"]=cgi.reqs["comment"];
		cgi.reqs["space0"]="";
	}else
	if("edit" in cgi.reqs){
		Dom dom=Dom.parse(Sample.read("tmpl/"~cgi.reqs["path"]~".page")).getDocument();
		Dom stack[];
		int depth=0;
		int i=0;
		while(true){
			char[] no = std.string.toString(i++);
			cgi.reqs["name"~no]=dom.value;
			cgi.reqs["comment"~no]=dom.getAttr("c");
			cgi.reqs["space"~no]=center("",depth*2);
			if(dom.array.length>0){
				stack ~= null;
				stack = stack ~ dom.array.dup.reverse;
				depth++;
			}
			if(stack.length==0)break;
			dom=stack[stack.length-1];stack.length=stack.length-1;
			if(dom===null){
				depth--;
				if(stack.length==0)break;
				dom=stack[stack.length-1];stack.length=stack.length-1;
			}
		}
	}else
	if("save" in cgi.reqs){
		Dom root = new Dom(Dom.ROOT,"");
		Dom[] doms;
		while(true){
			char[] no=std.string.toString(i);
			if(!("name"~no in cgi.reqs))break;
			if(cgi.reqs["name"~no]==""){
				i++;
				continue;
			}
			space= cgi.reqs["space"~no].length;
			if(space==0){
				Dom dom = newDom(Dom.VAR,cgi.reqs["name"~no])
					.setAttr("c",cgi.reqs["comment"~no]);
				root.add(dom);
				if(doms.length<1)doms.length=1;
				doms[0]=dom;
				mm++;mms=std.string.toString(mm);
			}else{
				Dom dom = newDom(Dom.VAR,cgi.reqs["name"~no])
					.setAttr("c",cgi.reqs["comment"~no]);
				doms[space/2-1].add(dom);
				if(doms.length<space/2+1)doms.length=space/2+1;
				doms[space/2]=dom;
				mm++;mms=std.string.toString(mm);
			}
			i++;
		}
		session["address"]=session["address"]~" > <a href=edit.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様編集</a>";
		int i=0;
		while(true){
			char[] no=std.string.toString(i);
			if(!("name"~no in cgi.reqs))break;
			session["edit_name"~no]=cgi.reqs["name"~no];
			session["edit_comment"~no]=cgi.reqs["comment"~no];
			session["edit_space"~no]=cgi.reqs["space"~no];
			i++;
		}
		session["edit_path"]=cgi.reqs["path"];
		Sample.writeSession(cgi,session);
		write("tmpl/"~cgi.reqs["path"]~".page",cast(void[])(root.toString()));
		print_editend(cgi,"editend.html");
		return;
	}else
	if(cgi.reqs["cmd"]=="load"){
		int i=0;
		cgi.reqs["path"]=session["edit_path"];
		while(true){
			char[] no=std.string.toString(i);
			if(!("edit_name"~no in session))break;
			cgi.reqs["name"~no]=session["edit_name"~no];
			cgi.reqs["comment"~no]=session["edit_comment"~no];
			cgi.reqs["space"~no]=session["edit_space"~no];
			i++;
		}
	}
	TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/edit");
	tmpl.setValue("address",session["address"]);
	i=0;
	while(true){
		char[] no=std.string.toString(i);
		if(!("name"~no in cgi.reqs))break;
		if(cgi.reqs["name"~no]==""){
			i++;
			continue;
		}
		space= cgi.reqs["space"~no].length;
		if(space==0){
			data["name"~mms]=cgi.reqs["name"~no];
			data["comment"~mms]=cgi.reqs["comment"~no];
			data["space"~mms]=center("",space);
			mm++;mms=std.string.toString(mm);
			data["name"~mms]="";
			data["comment"~mms]="";
			data["space"~mms]=center("",space+2);
			mm++;mms=std.string.toString(mm);
		}else{
			data["name"~mms]=cgi.reqs["name"~no];
			data["comment"~mms]=cgi.reqs["comment"~no];
			data["space"~mms]=center("  ",space);
			mm++;mms=std.string.toString(mm);
			data["name"~mms]="";
			data["comment"~mms]="";
			data["space"~mms]=center("",space+2);
			mm++;mms=std.string.toString(mm);
		}
		i++;
	}

	TmplDom f = tmpl.addBlock("f");
	f.setValue("path",cgi.reqs["path"]);
	for(int i=0;i<mm;i++){
		char[] no=std.string.toString(i);
		TmplDom dt = f.addBlock("dt");
		dt.setAttr("name","name","name"~no);
		dt.setAttr("path","name","path"~no);
		dt.setAttr("comment","name","comment"~no);
		dt.setAttr("up","name","up"~no);
		dt.setAttr("down","name","down"~no);
		dt.setAttr("space","name","space"~no);
		dt.setValue("name",data["name"~no]);
		dt.setValue("space",data["space"~no]);
		dt.setValue("path",data["path"~no]);
		dt.setValue("comment",data["comment"~no]);
	}

	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",tmpl.toString().replace("\r",""));
}
void print_editend(CGI cgi,char[] filename){
	char[][char[]] session=Sample.readSession(cgi);
	TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/editend");
	tmpl.setValue("address",session["address"]);
	tmpl.setValue("pagename",cgi.reqs["path"]);
	char[] xml=Dom.parse(Sample.read("tmpl/"~cgi.reqs["path"]~".page")).getDocument().toStringln();
	xml=Dom.xmlEscape(xml,1).replace("\r","");
	tmpl.setValue("xml",xml);
//	tmpl.setValue("xml","xml"~cast(char[])read("tmpl/"~cgi.reqs["path"]~".page"));
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",tmpl.toString().replace("\r",""));
}
void print_list(CGI cgi,char[] filename)
{
	char[][char[]] session;
	if("edit" in cgi.reqs){
		session["address"]="<a href=list.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様一覧</a>";
		Sample.writeSession(cgi,session);
		print_edit(cgi,"tmpl_pageedit/edit");
		return;
	}else
	if("detail" in cgi.reqs){
		session["address"]="<a href=list.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様一覧</a>";
		Sample.writeSession(cgi,session);
		print_detail(cgi,"tmpl_pageedit/detail");
		return;
	}
	TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/list");
	char[][] list=listdir("tmpl").sort.reverse;
//	printf("list length=%d\n",list.length);
	for(int i=0;i<list.length;i++){
		list[i]="tmpl/"~list[i];
	}
	while(list.length>0){
		char[] path=list[list.length-1];list.length=list.length-1;
		if(isdir(path)){
			char[][] l2=listdir(path).sort.reverse;
			for(int i=0;i<l2.length;i++){
				l2[i]=path~"/"~l2[i];
			}
			list = list ~ l2;
		}else{
			if(path.search(new RegExp(`\.page$`,""))!=-1){
				TmplDom f = tmpl.addBlock("f");
				char[] comment = "";
				try{
					comment=Dom.parse(Sample.read(path)).getDocument().getAttr("c");
				}catch(Exception e){comment=e.toString();}
				path=path[5..(path.length-5)];
				f.setValue("path",path);
				f.setValue("comment",comment);
				f.close();
//				printf("%.*s\n",path);
			}
		}
	}
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",tmpl.toString().replace("\r",""));
}
void print_detail(CGI cgi,char[] filename){
	char[][char[]] session=Sample.readSession(cgi);
	if("edit" in cgi.reqs){
		session["address"]=session["address"]~" > <a href=detail.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様詳細</a>";
//printf("content-type: text/html\n\nsession="~session["address"]);
		Sample.writeSession(cgi,session);
		print_edit(cgi,"tmpl_pageedit/edit");
		return;
	}
	TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/detail");
	tmpl.setValue("address",session["address"]);
	TmplDom f = tmpl.addBlock("f");
	char[] path="tmpl/"~cgi.reqs["path"]~".page";
	char[] comment = "";
	char[] spec= "";
	try{
		Dom dom = Dom.parse(Sample.read(path)).getDocument();
		comment= dom.getAttr("c");
		spec= Dom.xmlEscape(dom.toStringln(),1);
	}catch(Exception e){comment=e.toString();}
	path=path[5..(path.length-5)];
	f.setValue("spec",spec);
	f.setValue("path",path);
	f.setValue("comment",comment);
	f.close();
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",tmpl.toString().replace("\r",""));
}

void print_detaillist(CGI cgi,char[] filename){
	char[][char[]] session;
	if("edit" in cgi.reqs){
		session["address"]="<a href=detaillist.html?cmd=load&__session="~URI.encode(Sample.getSessionString(cgi))~r">ページ仕様詳細一覧</a>";
		Sample.writeSession(cgi,session);
		print_edit(cgi,"tmpl_pageedit/edit");
		return;
	}
	TmplDom tmpl = Sample.readTmplDom("tmpl_pageedit/detaillist");
	char[][] list=listdir("tmpl").sort.reverse;
//	printf("list length=%d\n",list.length);
	for(int i=0;i<list.length;i++){
		list[i]="tmpl/"~list[i];
	}
	while(list.length>0){
		char[] path=list[list.length-1];list.length=list.length-1;
		if(isdir(path)){
			char[][] l2=listdir(path).sort.reverse;
			for(int i=0;i<l2.length;i++){
				l2[i]=path~"/"~l2[i];
			}
			list = list ~ l2;
		}else{
			if(path.search(new RegExp(`\.page$`,""))!=-1){
				TmplDom f = tmpl.addBlock("f");
				char[] comment = "";
				char[] spec= "";
				try{
					Dom dom = Dom.parse(Sample.read(path)).getDocument();
					comment= dom.getAttr("c");
					spec= Dom.xmlEscape(dom.toStringln(),1);
				}catch(Exception e){comment=e.toString();}
				path=path[5..(path.length-5)];
				f.setValue("spec",spec);
				f.setValue("path",path);
				f.setValue("comment",comment);
				f.close();
//				printf("%.*s\n",path);
			}
		}
	}
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s",tmpl.toString().replace("\r",""));
}

// 対応するtest.xmlを読み出して、適用する。
void print_bbs_index(CGI cgi,char[] filename)
{
	char[] testname=cgi.reqs["test"];
	if(testname!=""){//testパラメータがあるときは、テストデータを使おうとする。
		char[] command = Sample.getCommand();
		char[] html = Sample.read("tmpl/"~filename);
		Dom dom;
		char[] testfilename = "tmpl/"~command~".test.xml";
		if(!isfile(testfilename)){// テストファイルが無ければエラー
			printf("%.*s\n",Sample.getHeader());
			printf("<html><body>file not found %.*s</body></html>\n",
				testfilename
			);
			return;
		}
		try{
			dom = Dom.parse(Sample.read(testfilename)).getDocument();
		}catch(Exception e){
			printf("%.*s\n",Sample.getHeader());
			printf("<html><body>perser error %.*s<pre>%.*s</pre></body></html>\n",testfilename,Dom.xmlEscape(e.toString(),0).replace("&#10;","\n"));
			return;
		}
		// testcase/test/@name=testnameを探す。
		Stack stack=new Stack();
		Dom test=null;
		if(dom.value=="testcase"){
			for(int i=0;i<dom.array.length;i++){
				if(dom.array[i].value=="model"){
					if(dom.array[i].getAttr("name")==testname){
						test=dom.array[i];
						break;
					}
				}
			}
		}
		if(test===null){// 該当testが見つからない。
			printf("%.*s\n",Sample.getHeader());
			//test一覧をリンク付きで表示しよう。
			printf("<html><body>not found test %.*s testname=%.*s</body></html>\n",testfilename,testname);
			return;
		}
		// とりあえず、テストケースのデータを表示する。
		printf("%.*s\n",Sample.getHeader());
		printf("<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=Shift_JIS\" />"
				"</head><body><pre>%.*s</pre></body></html>\n"
				,Dom.xmlEscape(test.toString(),1).replace("\r",""));
//html=`<html><T.error c="エラーメッセージがあるとき表示される"><$message></T.error></html>`;
		TmplDom tmpl = new TmplDom(html,"",command);
		tmpl.setDom(test);
		printf(tmpl.toString().replace("\r",""));
	}else{
		print_default(cgi,filename);
	}
}

void print_bbs_confilm(CGI cgi,char[] filename)
{
	printf("%.*s\n",Sample.getHeader());
	printf("%.*s","bbsconfilm");
}
