/*
 * Galatea Dialog Manager:
 * (c)2003-2004 Takuya NISHIMOTO (nishi@hil.t.u-tokyo.ac.jp)
 * Based on Phoenix By Takuya NISHIMOTO and Mitsuhiro KIZU
 *
 * $Id: VXMLDoc.java,v 1.17 2008/02/14 02:00:07 nishi Exp $
 */

package galatea.document;

import galatea.command.AddOutItemCommand;
import galatea.command.Command;
import galatea.command.CompositeCommand;
import galatea.command.EndCommand;
import galatea.command.EvaluateCommand;
import galatea.command.GotoCommand;
import galatea.command.SetNextCommand;
import galatea.command.StopOutputCommand;
import galatea.grammar.GrammarUtil;
import galatea.httpclient.NetUtil;
import galatea.logger.Logger;
import galatea.outitem.AudioOutItem;
import galatea.outitem.BreakOutItem;
import galatea.outitem.GrammarOutItem;
import galatea.outitem.LogOutItem;
import galatea.outitem.NativeOutItem;
import galatea.outitem.OutItem;
import galatea.outitem.VoiceOutItem;
import galatea.relaxer.vxml20.IVxmlFormChoice;
import galatea.relaxer.vxml20.IVxmlVxmlChoice;
import galatea.relaxer.vxml20.RString;
import galatea.relaxer.vxml20.URVisitor;
import galatea.relaxer.vxml20.VxmlAllOf;
import galatea.relaxer.vxml20.VxmlAssign;
import galatea.relaxer.vxml20.VxmlAudio;
import galatea.relaxer.vxml20.VxmlBlock;
import galatea.relaxer.vxml20.VxmlBreak;
import galatea.relaxer.vxml20.VxmlCONTEXT;
import galatea.relaxer.vxml20.VxmlCatch;
import galatea.relaxer.vxml20.VxmlChoice;
import galatea.relaxer.vxml20.VxmlClear;
import galatea.relaxer.vxml20.VxmlDisconnect;
import galatea.relaxer.vxml20.VxmlEMPH;
import galatea.relaxer.vxml20.VxmlElse;
import galatea.relaxer.vxml20.VxmlElseif;
import galatea.relaxer.vxml20.VxmlEmotion;
import galatea.relaxer.vxml20.VxmlEmphasis;
import galatea.relaxer.vxml20.VxmlEnumerate;
import galatea.relaxer.vxml20.VxmlError;
import galatea.relaxer.vxml20.VxmlExit;
import galatea.relaxer.vxml20.VxmlField;
import galatea.relaxer.vxml20.VxmlFilled;
import galatea.relaxer.vxml20.VxmlForm;
import galatea.relaxer.vxml20.VxmlGoto;
import galatea.relaxer.vxml20.VxmlGrammar;
import galatea.relaxer.vxml20.VxmlHelp;
import galatea.relaxer.vxml20.VxmlIf;
import galatea.relaxer.vxml20.VxmlInitial;
import galatea.relaxer.vxml20.VxmlJVOICE;
import galatea.relaxer.vxml20.VxmlLink;
import galatea.relaxer.vxml20.VxmlLog;
import galatea.relaxer.vxml20.VxmlMark;
import galatea.relaxer.vxml20.VxmlMedia;
import galatea.relaxer.vxml20.VxmlMenu;
import galatea.relaxer.vxml20.VxmlMeta;
import galatea.relaxer.vxml20.VxmlNative;
import galatea.relaxer.vxml20.VxmlNoinput;
import galatea.relaxer.vxml20.VxmlNomatch;
import galatea.relaxer.vxml20.VxmlObject;
import galatea.relaxer.vxml20.VxmlP;
import galatea.relaxer.vxml20.VxmlPITCH;
import galatea.relaxer.vxml20.VxmlPRON;
import galatea.relaxer.vxml20.VxmlParagraph;
import galatea.relaxer.vxml20.VxmlParam;
import galatea.relaxer.vxml20.VxmlPhoneme;
import galatea.relaxer.vxml20.VxmlPrompt;
import galatea.relaxer.vxml20.VxmlProperty;
import galatea.relaxer.vxml20.VxmlProsody;
import galatea.relaxer.vxml20.VxmlRATE;
import galatea.relaxer.vxml20.VxmlRecord;
import galatea.relaxer.vxml20.VxmlReprompt;
import galatea.relaxer.vxml20.VxmlReturn;
import galatea.relaxer.vxml20.VxmlS;
import galatea.relaxer.vxml20.VxmlSILENCE;
import galatea.relaxer.vxml20.VxmlSPELL;
import galatea.relaxer.vxml20.VxmlSayAs;
import galatea.relaxer.vxml20.VxmlScript;
import galatea.relaxer.vxml20.VxmlSentence;
import galatea.relaxer.vxml20.VxmlSubmit;
import galatea.relaxer.vxml20.VxmlThrow;
import galatea.relaxer.vxml20.VxmlTransfer;
import galatea.relaxer.vxml20.VxmlVOLUME;
import galatea.relaxer.vxml20.VxmlValue;
import galatea.relaxer.vxml20.VxmlVar;
import galatea.relaxer.vxml20.VxmlVoice;
import galatea.relaxer.vxml20.VxmlVxml;
import galatea.util.HashArray;
import galatea.util.Property;
import galatea.util.Util;

import java.util.ArrayList;
import java.util.Stack;


import org.w3c.dom.Element;


public class VXMLDoc implements IAbstractDoc // VoiceXML形式
{
	private final static String VXML_START_NAME = "@vxml_start";
	
	private Logger dbg = new Logger("VXMLDoc");
	private String docAddress_ = null;
	
	private StateMap stateMap_ = null;
	
	private ArrayList<DocError> docErrors_ = null;
	private ArrayList<DocWarning> docWarnings_ = null;
	
	private double promptTimeout_;
	private int unique_ = 0;
	
	private String _getEvalWith(String s, String w) {
		if (s == null) 
			return "true";
		if ( w.length() > 0 ) {
			// "with(" + w + "){" + s + "}"
			StringBuffer script = new StringBuffer("with(");
			script.append(w);
			script.append("){");
			script.append(s);
			script.append("}");
			return script.toString();
		} 
		return s;
	}
	
	
	private String _getUniqueId(String str)
	{
		unique_++;
		return "vxml_"+ str + unique_;
	}
	
//	private String getDocAddress()
//	{
//		return docAddress_;
//	}
	
//	public void setDocAddress(String adrs)
//	{
//		docAddress_ = adrs;
//	}
	
	/**
	 * ドキュメント中の相対pathを絶対pathに変換する
	 */
	private String resolveAdrs(String org)
	{
		return Util.resolveAdrs(docAddress_, org);
	}
	
	
	/**
	 * Visitor: <vxml> contents
	 */
	protected class Visitor extends galatea.relaxer.vxml20.RVisitorBase 
	{
		
		/**
		 * タグが置かれたコンテクスト（スコープ）を保持する
		 * @author nishi
		 */
		protected class Dialog {
			// for adding executable contents
			private String currFormName_ = null;   // weather
			
			public void setName(String s) {
				currFormName_ = s;		
			}
			public String getName() {
				return currFormName_;
			}
			
			// FormItem
			
			private String currFormItemName_ = null;   // place
			
			public void setItemName(String s) {
				currFormItemName_ = s;		
			}
			public String getItemName() {
				return currFormItemName_;
			}
			
		}
		
		private Dialog dialog_ = new Dialog();
		private HashArray formItems_ = null;
		/**
		 * var の初期化コマンド
		 */
		private String formVarInitScript_ = null; 
		
		/**
		 * object tree: VxmlXXX Object
		 */
		private Stack<Object> visitorStack_ = new Stack<Object>(); 
		
		/**
		 * prompt selection
		 */
		protected class Prompt {
			public int count = 1;
			public String cond = null;
			public ArrayList<Command> commands = null;
		}
		/**
		 * ArrayList of Prompt
		 */
		private ArrayList<Visitor.Prompt> promptList_ = null; 
		
		/**
		 * ArrayList of Command
		 */
		protected class Commands
		{
			private ArrayList<Command> cmdvec_ = null;
			
			public void setupCommands() {
				if ( cmdvec_ == null ) {
					cmdvec_ = new ArrayList<Command>();
				} else {
					String str = "";
					for (int i = 0, n = cmdvec_.size(); i < n; i++) {
						str += cmdvec_.get(i).toString();
					}
					docWarnings_.add(new DocWarning("commands not used before setupCommands()\n" + str));
				}
				// cmdvec_.add(new StopOutputCommand()); // bargein の後処理
			}
			
			public void appendCommands( Command c ) {
				dbg.print("appendCommands:" + c.toString() );
				if ( cmdvec_ != null ) {
					cmdvec_.add(c);
				}
			}
			
			/**
			 * 現在のcmdvec_を状態sにaddしてcmdvec_を初期化する
			 * @param s
			 */
			public void flushCommands( String s ) {
				if ( cmdvec_ != null && cmdvec_.size() > 0 ) {
					stateMap_.addCommand(s, cmdvec_);
					cmdvec_ = null;
				}
			}
			
			public ArrayList<Command> getCommands() {
				return cmdvec_;
			}
			
			/**
			 * cmdvec_をvで上書きする（prompt乗っ取り）
			 */
			public void setCommands(ArrayList<Command> v) {
				if (cmdvec_ != null && cmdvec_.size() > 0) {
					String str = "";
					for (int i = 0, n = cmdvec_.size(); i < n; i++) {
						str += cmdvec_.get(i).toString();
					}
					docWarnings_.add(new DocWarning("commands not used before setCommands(ArrayList v)\n" + str));
				}
				cmdvec_ = v;
				// cmdvec_.add(new StopOutputCommand()); // bargein の後処理
			}
			
			public void clearCommands() {
				if (cmdvec_ != null && cmdvec_.size() > 0) {
					String str = "";
					for (int i = 0, n = cmdvec_.size(); i < n; i++) {
						str += cmdvec_.get(i).toString();
					}
					docWarnings_.add(new DocWarning("commands not used before clearCommands()\n" + str));
				}
				cmdvec_ = null;
			}
		}
		
		Commands commands_ = new Commands();
		
		/**
		 * stack of executable content 
		 */
		protected class ExecContents
		{
			private Stack<Command> execContentsStack_ = new Stack<Command>();
			private int stackCount_ = 0;
			
			/**
			 * 条件condで実行されるCompositeCommandを実行コンテクストにpushする
			 * @param cond
			 */
			private void setupExecContent(String cond) {
				stackCount_++;
				dbg.print("setupExecContent: "+ stackCount_ + " " + cond);
				// create context
				ArrayList<Command> c = new ArrayList<Command>();
				CompositeCommand co = new CompositeCommand(c, cond);
				
				// save context
				execContentsStack_.push(co);
			}
			
			private void setupExecContent() {
				setupExecContent("true");
			}
			
			private void appendExecContent(Command c) {
				dbg.print("appendExecContent: "+ stackCount_ + " " + c);
				if ( ! execContentsStack_.empty() ) {
					CompositeCommand co = (CompositeCommand)execContentsStack_.peek();
					if ( co != null )
						co.getCommands().add(c);
				}
			}
			
			private void flushExecContent() {
				dbg.print("flushExecContent: " + stackCount_);
				stackCount_--;
				if ( ! execContentsStack_.empty() ) {
					CompositeCommand co = (CompositeCommand)execContentsStack_.pop();
					//if ( co != null )
					// commands_.appendCommands(co);
					if ( execContentsStack_.empty() ) {
						commands_.appendCommands(co);
					} else {
						CompositeCommand c = (CompositeCommand)execContentsStack_.peek();
						c.getCommands().add(co);
					}
				}
			}
		}
		
		ExecContents execContents_ = new ExecContents();
		
		/**
		 * for speak buffer / log buffer / native command buffer
		 */
		protected class TextBuf
		{
			/**
			 * 
			 * @author nishi
			 *
			 */
			private class BaseText
			{
				private StringBuffer text_ = null;
				
				public void resetText() { 
					text_ = new StringBuffer();
				}
				
				public StringBuffer getText() { 
					if (text_ == null) {
						text_ = new StringBuffer("");
					}
					return text_; 
				}
				
				public void setText( StringBuffer text ) { 
					text_ = text;
				}
				
				public void appendString(String text) {
					dbg.ASSERT(context_ != null, "context_ null error");
					StringBuffer sb = context_.getText();
					if ( sb != null && text != null && text.length() > 0 ) {
						if ( sb.length() > 0 ) {
							sb.append("+");
						}
						sb.append("'");
						sb.append(text);
						sb.append("'");
						context_.setText(sb);
					}
				}
				
				public void appendValue(String expr) {
					dbg.ASSERT(context_ != null, "context_ null error");
					StringBuffer sb = context_.getText();
					if ( sb != null && expr != null && expr.length() > 0 ) {
						if ( sb.length() > 0 ) {
							sb.append("+");
						}
						sb.append(expr);
						context_.setText(sb);
					}
				}
				
			}
			
			/**
			 * 
			 * @author nishi
			 *
			 */
			private class SpeakText extends BaseText 
			{
				public void appendString(String text) {
					String s = Util.xmlSafeRemoveSpaces(text);
					super.appendString(s);
				}
			}
			
			/**
			 * 
			 * @author nishi
			 *
			 */
			private class LogText extends BaseText 
			{
				public void appendString(String text) {
					String s = Util.removeNewLines(text);
					super.appendString(s);
				}
			}
			
			/**
			 * 
			 * @author nishi
			 *
			 */
			private class NativeText extends BaseText
			{
			}
			
			private BaseText speakText_  = new SpeakText();
			private BaseText logText_    = new LogText();
			private BaseText nativeText_ = new NativeText();
			
			private BaseText context_ = speakText_;
			
			public void appendString(String text) {
				context_.appendString(text);
			}
			
			public void appendValue(String expr) {
				context_.appendValue(expr);
			}
			
			public void setupSpeak() {
				context_ = speakText_;
				context_.resetText();
			}
			
			public void flushSpeak() {
				dbg.ASSERT(context_ instanceof SpeakText, "error");
				StringBuffer sb = context_.getText();
				if ( sb != null ) {
					String text = new String(sb).trim();
					if (text.length() > 0) {
						VoiceOutItem item = new VoiceOutItem
						(text, dialog_.getName(), currPromptBargein_);
						//item.setEval(true);
						Command c = new AddOutItemCommand(item);
						execContents_.appendExecContent(c);
					}
					context_.resetText();
				}
			}
			
			public void setupLog() {
				context_ = logText_;
				context_.resetText();
			}
			
			public void flushLog() {
				dbg.ASSERT(context_ instanceof LogText, "error flushLog()");
				StringBuffer log = context_.getText();
				if ( log  != null ) {
					String text = new String(log).trim();
					if (text.length() > 0) {
						LogOutItem item = new LogOutItem(text, dialog_.getName());
						//item.setEval(true);
						Command c = new AddOutItemCommand(item);
						execContents_.appendExecContent(c);
					}
					context_.resetText();
				}
				context_ = speakText_;
			}
			
			public void setupNative() {
				context_ = nativeText_;
				context_.resetText();
			}
			
			public void flushNative() {
				dbg.ASSERT(context_ instanceof NativeText, "error flushNative()");
				StringBuffer sb = context_.getText();
				if ( sb != null ) {
					String text = new String(sb).trim();
					if (text.length() > 0) {
						NativeOutItem item = new NativeOutItem(text, dialog_.getName());
						//item.setEval(true);
						Command c = new AddOutItemCommand(item);
						execContents_.appendExecContent(c);
					}
					context_.resetText();
				}
				context_ = speakText_;
			}
			
		}
		
		TextBuf textBuf_ = new TextBuf();
		
		// vxml
		
		public boolean enter(VxmlVxml v) {
			_resetDocumentProperty();
			visitorStack_.push(v);
			return true;
		}
		
		public void leave(VxmlVxml v) {
			visitorStack_.pop();
		}
		
		
		/**
		 * parent may instanceof VxmlVxml/VxmlChoice/VxmlForm/VxmlField
		 * <grammar type="application/srgs+xml" src="xxx.grxml" /> (default)
		 * <grammar type="application/julian"   src="xxx" />
		 */
		public boolean enter(VxmlGrammar gram) {
			String type = gram.getType();
			String cmd1 = "";
			String cmd2 = "";
			
			if (type != null && type.equals("application/julian")) {
				cmd1 = "'to @SRM set Grammar = " + gram.getSrc() + ".dfa'";
				cmd2 = "'to @SRM set Dic = " + gram.getSrc() + ".dict'";
			} else {
				
				String root = gram.getRoot();
				String id = "";
				if (root != null && root.startsWith("#")) {
					
//					if (visitorStack_.peek() instanceof VxmlForm) {
//						id = dialog_.getName() + "_" + root.replaceAll("#", "");
//					} else if (visitorStack_.peek() instanceof VxmlField) {
//						id = dialog_.getName() + "_" + dialog_.getItemName() + "_" + root.replaceAll("#", "");
//					}
					id = _getUniqueId("rule");
					
				} else {
					// assert(false)
					id = _getUniqueId("rule");
				}
				
				// コンパイルは document top で行う
				String g = GrammarUtil.getJulianXmlGrammar(gram, docAddress_);
				GrammarOutItem o1 = new GrammarOutItem(g, id);
				Command c1 = (Command)( new AddOutItemCommand(o1) );
				stateMap_.addCommand(VXML_START_NAME, c1);
				
				// 実際の変更コマンド
				
				cmd1 = "'to @SRM set Grammar = " + o1.getLocation() + ".dfa'";
				cmd2 = "'to @SRM set Dic = " + o1.getLocation() + ".dict'";
			}
			
			// to @SRM set Run = RESUME
			// NativeOutItem o3 = new NativeOutItem("'to @SRM set Run = RESUME'");
			// Command c3 = (Command)( new AddOutItemCommand(o3) );
			
			// to @SRM set Grammar をどこで行うか
			String state = "";
			if (visitorStack_.peek() instanceof VxmlForm) {
				state = "@" + dialog_.getName();
			} else if (visitorStack_.peek() instanceof VxmlField) {
				state = "@" + dialog_.getName() + "." + dialog_.getItemName();
			}
			// 文法の ADDGRAM/DELGRAM をうまく使う??
			NativeOutItem o2 = new NativeOutItem(cmd1);
			Command c2 = (Command)( new AddOutItemCommand(o2) );
			NativeOutItem o3 = new NativeOutItem(cmd2);
			Command c3 = (Command)( new AddOutItemCommand(o3) );
			stateMap_.addCommand(state, c2);
			stateMap_.addCommand(state, c3);
			
			return true;
		}
		
		// <form>
		
		/**
		 * 
		 * formState を生成する。
		 * 初期化スクリプト: weather = {next$:undefined, nextitem$:undefined};
		 * 
		 * フィールド初期化スクリプト:
		 * 
		 * フィールド初期化(native)：
		 * 'to SIM set DialogName = ask'
		 * 'to SIM set SlotAlias = RESET'
		 * 
		 * 各フィールドごとにname属性とslot属性に対応するコマンドを生成：
		 * &lt;field name="confirm" slot="yesno"&gt;
		 * 'to SIM set SlotAlias = yesno confirm'
		 *
		 */
		private String _makeFormState() {
			// [@weather]
			String form = "@" + dialog_.getName();
			String t = stateMap_.newState(form);
			dbg.ASSERT(t.equals(form), "VxmlForm#leave");
			
			String dname = dialog_.getName();
			String sc;
			sc = dname + "={next$:undefined, nextitem$:undefined}; ";
			sc += formVarInitScript_;
			
			for (int i = 0, n = formItems_.size(); i < n; i++) {
				String name = formItems_.getKey(i);
				Object fi = formItems_.get(i);
				String fname  = dialog_.getName() + "." + name;
				String guardcond = "false"; // true:skipped
				String shadow = fname + "$";
				if ( fi instanceof VxmlField ) {
					// field expr="initial_value"
					VxmlField field = (VxmlField)fi;
					if (field.getExpr() != null) {
						sc += fname + " = " + field.getExpr() + "; ";
					} else {
						sc += fname + " = undefined; ";
					}
					if (field.getCond() != null) {
						guardcond = field.getCond();
					}
					sc += shadow + "={" +
							"guardcond:" + guardcond + ", " +
							"justfilled:false, " +
							"promptcount:1, " +
							"promptcond:true}; ";
				} else if ( fi instanceof VxmlBlock ) {
					VxmlBlock block = (VxmlBlock)fi;
					if (block.getCond() != null) {
						guardcond = block.getCond();
					}
					sc += shadow + "={guardcond:" + guardcond + "}; ";
				} else if ( fi instanceof VxmlInitial ) {
					VxmlInitial initial = (VxmlInitial)fi;
					if (initial.getCond() != null) {
						guardcond = initial.getCond();
					}
					sc += shadow + "={guardcond:" + guardcond + "}; ";
				}
			}
			stateMap_.addScriptCommand(form, sc);
			
			ArrayList<OutItem> items = new ArrayList<OutItem>();
			String s;
			s = "'to @SIM set DialogName = " + dialog_.getName() + "'";
			items.add(new NativeOutItem(s));
			s = "'to @SIM set SlotAlias = RESET'";
			items.add(new NativeOutItem(s));
			for ( int i = 0; i < formItems_.size(); i++ ) {
				Object fi = formItems_.get(i);
				if ( fi instanceof VxmlField ) {
					VxmlField f = (VxmlField)fi;
					if (f.getSlot() != null) {
						String name = formItems_.getKey(i);
						s = "'to @SIM set SlotAlias = " + f.getSlot()+ " " + name + "'";
						items.add(new NativeOutItem(s));
					}
				}
			}
			stateMap_.addCommand(form, new AddOutItemCommand(items));
			
			return form;
		}
		
		/**
		 * 
		 * @return selectStateの状態名
		 */
		private String _makeFormSelectState() {
			// @weather.vxml_select
			String select = "@" + dialog_.getName() + ".vxml_select";
			String t = stateMap_.newState(select);
			dbg.ASSERT(t.equals(select), "VxmlForm#leave");
			
			String sc = ""; 
			// TODO: goto nextitem
			// if (dname.nextitem$ != undefined) { dname.next$ = '@' + dname.nextitem$;}
			//
			for ( int i = 0; i < formItems_.size(); i++ ) {
				String name = formItems_.getKey(i);
				if ( i > 0 ) {
					sc += "} else ";
				}
				String dname = dialog_.getName();
				String fname = dname + "." + name;
				String sname = fname + "$";
				sc += "if (" + sname + ".guardcond==false && " + fname + "==undefined) {";
				sc += dname + ".next$ = '@" + fname + "';";
			}
			sc += "}"; 
			
			stateMap_.addCommand(select, new EvaluateCommand(sc));
			stateMap_.addCommand(select, new StopOutputCommand());
			
			for ( int i = 0; i < formItems_.size(); i++ ) {
				String name = formItems_.getKey(i);
				Object fi = formItems_.get(i);
				if ( fi instanceof VxmlField ) {
					sc = dialog_.getName() + "." + name + "$.justfilled=false;";
					stateMap_.addCommand(select, new EvaluateCommand(sc));
				}
			}
			
			// shadow変数の値を評価して遷移先の状態名とする
			sc = dialog_.getName() + ".next$";
			stateMap_.addCommand(select, new SetNextCommand(sc));
			
			return select;
		}
		
		/**
		 * 
		 * @return state name (formState.vxml_process)
		 */
		private String _makeFormFilledState() {
			// @weather.vxml_process
			String filled = "@" + dialog_.getName() + ".vxml_process";
			String t = stateMap_.newState(filled);
			dbg.ASSERT(t.equals(filled), "VxmlForm#leave");
			return filled;
		}
		
		
		private HashArray submitItems_; // for submit
		
		/**
		 * vxmlFormの中のvarとfieldのリストをsubmitItems_に格納する
		 * @param vxmlForm
		 */
		private void _setupSubmitItems(VxmlForm vxmlForm) 	{
			submitItems_ = new HashArray();
			for (int i = 0; i < vxmlForm.sizeContent(); i++) {
				IVxmlFormChoice c = vxmlForm.getContent(i);
				if (c instanceof VxmlVar) {
					submitItems_.put(((VxmlVar)c).getName(), (Object)c);
				} else if (c instanceof VxmlField) {
					submitItems_.put(((VxmlField)c).getName(), (Object)c);
				}
			}
		}
		
		
		public boolean enter(VxmlForm vxmlForm) {
			String dialogId = vxmlForm.getId();
			if (dialogId == null) dialogId = "";
			
			dialog_.setName(dialogId);
			formItems_ = new HashArray();
			formVarInitScript_ = "";
			
			_setupSubmitItems(vxmlForm);
			
			_resetDialogProperty();
			
			visitorStack_.push(vxmlForm);
			return(true);
		}
		
		public void leave(VxmlForm o) {
			String form = _makeFormState();
			String select = _makeFormSelectState();
			String filled = _makeFormFilledState();
			
			stateMap_.addSetNextCommand(form, select);
			stateMap_.addSetNextCommand(filled, select);
			
			// _makeDialogCommonStates();
			
			// test
			// stateMap_.addSetNextCommand(select, "@weather.place");
			
			visitorStack_.pop(); 
		}
		
		// block
		//<state id="@weather.block1" next="@weather.vxml_select">
		//<script>weather.block1 = true;</script>
		//<add><voice with="weather">'天気情報サービスへようこそ．'</voice></add>
		//</state>
		
		public boolean enter(VxmlBlock vxmlBlock) {
			if ( vxmlBlock.getName() == null ) {
				dialog_.setItemName(_getUniqueId("block")); // s1
			} else {
				dialog_.setItemName(vxmlBlock.getName());
			}
			
			String s = "@" + dialog_.getName() + "." + dialog_.getItemName();
			String t = stateMap_.newState(s);
			// @weather.s1
			dbg.ASSERT(t.equals(s), "VxmlBlock#enter");
			
//			String dialog = "@" + dialog_.getName();
			
			// stateMap_.addTrans(s, dialog + ".vxml_pause", "VXML_PAUSE");
			// stateMap_.addTrans(s, dialog + ".vxml_restart", "VXML_RESTART");
			
			stateMap_.addCommand(s, new EvaluateCommand
					(dialog_.getName() + "." + dialog_.getItemName() + "=true;"));
			
			visitorStack_.push(vxmlBlock);
			formItems_.put(dialog_.getItemName(), (Object)vxmlBlock);
			
			commands_.setupCommands();
			execContents_.setupExecContent();
			textBuf_.setupSpeak();
			return true;
		}
		
		public void leave(VxmlBlock vxmlBlock) {
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			String s = "@" + dialog_.getName() + "." + dialog_.getItemName();
			commands_.flushCommands(s);
			stateMap_.addSetNextCommand(s, "@" + dialog_.getName() + ".vxml_select");
			
			visitorStack_.pop(); 
		}
		
		
		// <filled>
		public boolean enter(VxmlFilled e) {
			String cond = "true";
			dbg.print("VXMLDoc:enter#VxmlFilled peek:" + visitorStack_.peek());
			commands_.setupCommands();
			
			if (visitorStack_.peek() instanceof VxmlField) {
				String item = dialog_.getName() + "." + dialog_.getItemName();
				cond = item + "$.justfilled";
				cond += " && " + item + "!=undefined";
			} else {
				// with(form){item1 != undefined && item2 != undefined}
				cond = "with("+dialog_.getName()+"){";
				for ( int i = 0; i < formItems_.size(); i++ ) {
					String name = formItems_.getKey(i);
					if ( i > 0 ) {
						cond += " && ";
					}
					cond += name +"!=undefined";
				}
				cond += "}";
			}
			execContents_.setupExecContent(cond);
			
			textBuf_.setupSpeak();
			visitorStack_.push(e);
			return true;
		}
		
		public void leave(VxmlFilled e) {
			// [@weather.vxml_process]
			visitorStack_.pop(); 
			textBuf_.flushSpeak();
			
			execContents_.flushExecContent();
			
			String filled = "@" + dialog_.getName() + ".vxml_process"; 
			commands_.flushCommands(filled); 
		}
		
		
		// <field>
		
		/**
		 * fieldごとに入力後の状態を生成：
		 * fieldState.noinput, fieldState.grammarN
		 */
		private void _makeInputEventHandler(String field) {
			// catch pause, resume, restart
//			String dialog = "@" + dialog_.getName();
			
			//stateMap_.addTrans(field, dialog + ".vxml_pause", "VXML_PAUSE");
			//stateMap_.addTrans(field, dialog + ".vxml_restart", "VXML_RESTART");
			//stateMap_.addTrans(field, dialog + ".vxml_resume", "VXML_RESUME");
			
			String select = "@" + dialog_.getName() + ".vxml_select";
			
			// VXML catch 実行後は prompt を停止する (repromptで再開)
			// [@main.place.noinput] main.place$.promptcond = false;
			//String nopromptScript = dialog_.getName() + "." 
			// + dialog_.getItemName() + "$" + ".promptcond = false;";
			
			// [@main.place.noinput]
			String noinput = stateMap_.newState(field + ".noinput");
			stateMap_.addSetNextCommand(field, noinput);
			stateMap_.addSetNextCommand(noinput, select);
			
			// TODO: nomatch, help, error
			
			/*
			 * <state id="@form1.source">
			 * <catch next="@form1.source.vxml_grammar2"/>
			 * <cmd>
			 * <next>'@form1.source.noinput'</next>
			 * ... omitted ...
			 * 
			 * <state id="@form1.source.vxml_grammar2">
			 * <cmd>
			 * <script>form1.source$.utterance = $utterance;</script>
			 * <next>'@form1.vxml_process'</next>
			 * </cmd>
			 * </state>
			 */
			// weather.place
			String formitem = dialog_.getName() + "." + dialog_.getItemName(); 
			// @weather.vxml_process
			String filled = "@" + dialog_.getName() + ".vxml_process"; 
			// @weather.place.grammar1
			String s = "@" + formitem + "." + _getUniqueId("grammar"); 
			String newID = stateMap_.newState(s);   
			// @weather.place -> @weather.place.grammar1
			stateMap_.addDefaultTrans("@" + formitem, newID); 
			stateMap_.addCommand(newID, new EvaluateCommand(
					formitem + "$.utterance=$utterance;"));
			// @weather.place.grammar1 -> @weather.vxml_process
			stateMap_.addSetNextCommand(newID, filled); 
			
		}
		
		public boolean enter(VxmlField vxmlField) {
			// field.name (place)
			if ( vxmlField.getName() == null ) {
				dialog_.setItemName(_getUniqueId("field"));
			} else {
				if ( formItems_.has(vxmlField.getName()) ) {
					docErrors_.add(new DocError("Field name dupulication."));
					return false;
				}
				dialog_.setItemName(vxmlField.getName());
			}
			
			formItems_.put(dialog_.getItemName(), (Object)vxmlField);
			
			dbg.ASSERT(dialog_.getItemName() != null, "vxmlForm#enter");
			
			// [@main.place]
			String f = "@" + dialog_.getName() + "." + dialog_.getItemName(); 
			String field = stateMap_.newState(f);
			
			if ( vxmlField.getExpr() != null ) {
				String script = dialog_.getName() + "." + dialog_.getItemName() 
				+ " = " + vxmlField.getExpr() + ";"; 
				stateMap_.addScriptCommand(field, script);
			}

			String fieldType = vxmlField.getType();
			if (fieldType != null && fieldType.startsWith("digits")) {
				String msg = "'to @KBD set FieldName = " 
					+ dialog_.getName() + "." + vxmlField.getName() + "'";
				stateMap_.addOutputNativeCommand(field, msg);
				msg = "'to @KBD set FieldType = " + vxmlField.getType() + "'";
				stateMap_.addOutputNativeCommand(field, msg);
			}
			
			_makeInputEventHandler(field);
			
			// initialize promptList_
			promptList_ = new ArrayList<Visitor.Prompt>();
			
			visitorStack_.push(vxmlField);
			return true;
		}
		
		/**
		 * 
		 *
		 */
		private void _makePromptSelect() {
			
			// [@main.place]
			
			String field = "@"+dialog_.getName()+"."+dialog_.getItemName(); 
			
			// default noinput handler
			//stateMap_.addSetNextCommand(field, field);
			
			// ここから prompt 生成
			String promptCond = dialog_.getName() + "." 
			+ dialog_.getItemName() + "$" + ".promptcond";
			//promptCond = "true"; // debug
			CompositeCommand promptCmd = new CompositeCommand
			(new ArrayList<Command>(), promptCond);
			
			if (promptList_ == null) {
				return; 
				// leave Prompt で timeout が付加されるので
				// 通常はあり得ない
			}
			
			// script with="main.place$"
			String sWith = dialog_.getName()+"."+dialog_.getItemName()+"$";
			
			// 初期化しながら cond == false を削除
			//<script with="main.place$">
			//  p1 = true;    
			//  p2 = true;    
			//</script>
			
			String script = "";
			for (int i = 0; i < promptList_.size(); i++) {
				Prompt prompt = (Prompt)promptList_.get(i);
				if ( prompt.cond == null ) {
					script += "p" + i + "=true;";
				} else {
					script += "p" + i + "=(" + prompt.cond + ");";
				}
			}
			promptCmd.addScriptCommand(script, sWith);
			
			// cc := 現在のカウント値以下で，各要素の count 属性の最大値
			//   <script with="main.place$">
			//     cc = 1;	    
			//     // p1: count=1
			//     // p2: count=3
			//     if ( 1 <= promptcount && cc < 1 ) {cc = 1};
			//     if ( 3 <= promptcount && cc < 3 ) {cc = 3};
			//   </script>
			script = "cc=1;";
			for (int i = 0; i < promptList_.size(); i++) {
				Prompt prompt = (Prompt)promptList_.get(i);
				int n = prompt.count;
				script += "if (" + n + "<=promptcount && cc<" + n + ") {cc=" + n + "};";
			}
			promptCmd.addScriptCommand(script, sWith);
			
			// cc と同じ count 値を持たない要素をリストから削除
			// ただし cond で削除されたものはあらかじめ false
			//<script with="confirm._menu_$">
			//     if ( 1 != cc ) { p1=false };
			//     if ( 3 != cc ) { p2=false };
			//</script>
			script = "";
			for (int i = 0; i < promptList_.size(); i++) {
				Prompt prompt = (Prompt)promptList_.get(i);
				int n = prompt.count;
				script += "if (" + n + "!=cc){p" + i + "=false};";
			}
			promptCmd.addScriptCommand(script, sWith);
			
			
			// バージイン対策 ここでincする
			//  <script>main.place$.promptcount++;</script>
			script = dialog_.getName() + "." + dialog_.getItemName() + "$" + 
			".promptcount++;";
			promptCmd.addScriptCommand(script);
			
			// for debug
			//String text ="'promptcount='+promptcount";
			//LogOutItem item = new LogOutItem(text, sWith);
			//item.setEval(true);
			//Command logcmd = new AddOutItemCommand(item);
			//promptCmd.addCommand(logcmd);
			
			// execute prompt list 
			//  <cmd cond="main.place$.p1">
			//   <add><voice with="main">'場所をどうぞ。'</voice></add>
			//   <add><break length="10.0"/></add>
			//  </cmd>
			//  以下同様
			for (int i = 0; i < promptList_.size(); i++) {
				Prompt prompt = (Prompt)promptList_.get(i);
				String cond = "with (" + sWith + ") {p" + i +"}";
				if (prompt.commands.size() == 1 
						&& prompt.commands.get(0) instanceof CompositeCommand ) {
					// 冗長な入れ子を防ぐ
					Command co = (Command)prompt.commands.get(0);
					co.setCondition(cond);
					promptCmd.addCommand(co);
				} else {
					CompositeCommand co = new CompositeCommand
					(prompt.commands, cond);
					promptCmd.addCommand(co);
				}
			}
			
			// promptCmd を field に追加
			stateMap_.addCommand(field, promptCmd);
			
			// timeout
			Command c = new AddOutItemCommand
			(new BreakOutItem("$prompt_timeout", currPromptBargein_));
			stateMap_.addCommand(field, c);
		}
		
		public void leave(VxmlField vxmlField) {
			visitorStack_.pop(); 
			
			_makePromptSelect();
		}
		
		// <var> は Block や Field と対等なので ExecContext ではない
		// state(@form) に
		//   <script>form.name=expr</script>
		// または
		//   <script>form.name=undefined</script>
		// を append する (formVarInitScript_)
		public boolean enter(VxmlVar e) {
			if ( e.getName() == null ) {
				return false;
			}
			String aname = e.getName();
			String aexpr = "undefined";
			if ( e.getExpr() != null ) {
				aexpr = e.getExpr();
			}
			if (visitorStack_.peek() instanceof VxmlVxml) {
				String sc = _getEvalWith(aname + "=" + aexpr, "document");
				formVarInitScript_ += sc + ";";
			} else if (visitorStack_.peek() instanceof VxmlForm) {
				String sc = _getEvalWith(aname + "=" + aexpr, dialog_.getName());
				formVarInitScript_ += sc + ";";
			}
			return true;
		}
		
		public boolean enter(VxmlMenu vxmlMenu) {
			visitorStack_.push(vxmlMenu);
			
			// initialize promptList_
			promptList_ = new ArrayList<Visitor.Prompt>();
			_resetDialogProperty();

			String dialogName;
			// menu_id . field
			if (vxmlMenu.getId() == null) {
				dialogName = _getUniqueId("menu");
			} else {
				dialogName = vxmlMenu.getId();
			}
			dialog_.setName(dialogName);
			
			String fieldName = "field";
			dialog_.setItemName(fieldName);
			
			// [@menu_id]
			String dialogState = "@" + dialogName;
			stateMap_.newState(dialogState);
			
			String selectState = dialogState + ".vxml_select"; 
			stateMap_.newState(selectState);
			
			// [@menu_id.field]
			String fieldState = dialogState + "." + fieldName;  
			stateMap_.newState(fieldState);
			
			stateMap_.addSetNextCommand(dialogState, selectState);
			stateMap_.addSetNextCommand(selectState, fieldState);
			
			stateMap_.addScriptCommand(dialogState, 
					dialogName + "={" + fieldName + "$:{promptcount:1,promptcond:true}};");
			
			//ArrayList choices = new ArrayList();
//			for (int i=0, n=vxmlMenu.sizeContent(); i<n; i++) {
//				Object obj = vxmlMenu.getContent(i);
//				if (obj instanceof VxmlChoice) {
//					//choices.add(obj);
//				}
//			}
			GrammarOutItem o1 = new GrammarOutItem("g", dialogName);
			Command c1 = (Command)( new AddOutItemCommand(o1) );
			stateMap_.addCommand(VXMLDoc.VXML_START_NAME, c1);

			return true;
		}
		
		public void leave(VxmlMenu menu) {
			visitorStack_.pop(); 
			_makePromptSelect();
		}
		
//		public boolean enter(VxmlMenu vxmlMenu) {
//			// menu.id (confirm)
//			if ( vxmlMenu.getId() == null ) {
//				dialog_.setName(_getUniqueId("menu"));
//			} else {
//				dialog_.setName(vxmlMenu.getId());
//			}
//			dialog_.setItemName("_menu_");
//			
//			// [@dialog]
//			String m = "@" + dialog_.getName();
//			String menu = stateMap_.newState(m);
//			dbg.ASSERT(m.equals(menu), "vxmlMenu#enter");
//			
//			// [@dialog.vxml_select]
//			// <state id="@dialog.vxml_select">
//			// <cmd><next>'@dialog._menu_'</next></cmd>
//			// </state>
//			String select = stateMap_.newState(menu + ".vxml_select");
//			
//			// [@dialog._menu_]
//			String f = "@"+dialog_.getName()+"."+dialog_.getItemName();  
//			String field = stateMap_.newState(f);
//			dbg.ASSERT(f.equals(field), "vxmlMenu#enter");
//			
//			stateMap_.addSetNextCommand(menu, select);
//			stateMap_.addSetNextCommand(select, field);
//			
//			_makeInputEventHandler(field);
//			
//			// initialize promptList_
//			promptList_ = new ArrayList();
//			visitorStack_.push(vxmlMenu);
//			
//			_resetDialogProperty();
//			
//			return true;
//		}
//		
//		public void leave(VxmlMenu o) { 
//			visitorStack_.pop(); 
//			
//			String form  = "@" + dialog_.getName();
//			String field = form + "." + dialog_.getItemName();  
//			
//			// [@confirm]
//			String script;
//			script  = dialog_.getName() + " = new Object();\n";
//			String shadow = dialog_.getName() + "._menu_$";
//			script += shadow + " = new Object();\n";
//			script += shadow + ".promptcount = 1;\n";
//			script += shadow + ".promptcond = true;";
//			stateMap_.addScriptCommand(form, script);
//			stateMap_.addSetNextCommand(form, field);
//			
//			_makePromptSelect();
//			
//			// _makeDialogCommonStates();
//		}
		
		// choice (in menu)
		
		public boolean enter(VxmlChoice vxmlChoice) {
			visitorStack_.push(vxmlChoice);
			String next = null;
			String n = vxmlChoice.getNext();
			if (n == null) {
				docErrors_.add(new DocError("No next in choice."));
				return false;
			}
			next = Util.removeSpaces(n);
			
			String image = "";
			String im = vxmlChoice.getImage();
			if(im != null) {
				image = Util.removeSpaces(im);
				image = resolveAdrs(image);
				dbg.ASSERT( image != null, "VxmlChoice#enter");
			}
			
			String menuState = "@" + dialog_.getName() + "._menu_";
			
			for ( int i = 0; i < vxmlChoice.sizeContent(); i++ ) {
				String text = DocUtil.getText(vxmlChoice.getContent(i));
				dbg.ASSERT( text != null, "VxmlChoice#enter");
				
				if(text.length() > 0 ) {
					
					if(next.startsWith("#")) {
						String nextState = "@" + next.substring(1);
						stateMap_.addTrans(menuState, nextState, text);
						/*
						 } else if(next.startsWith("http://")) {
						 Command c = null;
						 try {
						 c = new HttpGetCommand(new URL(next));
						 } catch (Exception e) { e.printStackTrace(); }
						 stateMap_.addCommand(menuState, c);
						 */
					} 
				}
			} 
			
			return(true);
		}
		
		public void leave(VxmlChoice vxmlChoice) {
			visitorStack_.pop();
		}

		// <prompt>
		
		private String documentBargeinProperty_ = null;
		private String dialogBargeinProperty_ = null;
		
		/**
		 * documentBargeinProperty_ の初期化 
		 *
		 */
		private void _resetDocumentProperty()
		{
			documentBargeinProperty_ = null;
		}
		
		/**
		 * dialogBargeinProperty_ の初期化 
		 *
		 */
		private void _resetDialogProperty()
		{
			dialogBargeinProperty_ = null;
		}
		
		private String currPromptCond_  = null;
		private String currPromptCount_ = null;
		
		private boolean currPromptBargein_ = true;
//		private String  currPromptBargeintype_ = "speech"; // speech, hotword
//		private String  currPromptTimeout_ = null;
		
		public boolean enter(VxmlPrompt vxmlPrompt) {
			
			if ( vxmlPrompt.getBargein() != null ) {
				currPromptBargein_ = Boolean.getBoolean(vxmlPrompt.getBargein());
			} else {
				if ( dialogBargeinProperty_ != null ) {
					currPromptBargein_ = Boolean.getBoolean(dialogBargeinProperty_);
				} else if ( documentBargeinProperty_ != null ) {
					currPromptBargein_ = Boolean.getBoolean(documentBargeinProperty_);
				}
			}
//			if ( vxmlPrompt.getBargeintype() != null )
//				currPromptBargeintype_ = vxmlPrompt.getBargeintype();
			
			if (visitorStack_.peek() instanceof VxmlMenu
					|| visitorStack_.peek() instanceof VxmlField) {
				currPromptCond_ = vxmlPrompt.getCond();
				currPromptCount_ = vxmlPrompt.getCount();
				
				commands_.setCommands(new ArrayList<Command>());
				
				execContents_.setupExecContent();
			}
			
			if (vxmlPrompt.getTimeout() != null ) {
				int msec = DocUtil.getCSS2TimesAsMsec(vxmlPrompt.getTimeout());
				String script = "$prompt_timeout="+Integer.toString(msec);
				commands_.appendCommands(new EvaluateCommand(script));
			}
			
			textBuf_.setupSpeak();
			return(true);
		}
		
		public void leave(VxmlPrompt vxmlPrompt) {
			textBuf_.flushSpeak();
			
			if (visitorStack_.peek() instanceof VxmlMenu
					|| visitorStack_.peek() instanceof VxmlField) {
				
				execContents_.flushExecContent();
				
				// flushCommands しないで prompt.commands に追加する
				// prompt を promptList_ に追加
				
				Prompt prompt = new Prompt();
				
				if (currPromptCond_ == null ) {
					prompt.cond = "true";
				} else {
					prompt.cond = currPromptCond_;
				}
				
				if (currPromptCount_ == null ) {
					prompt.count = 1;
				} else {
					prompt.count=Integer.valueOf(currPromptCount_).intValue();
				}
				
				prompt.commands = commands_.getCommands();
				commands_.clearCommands();
				
				promptList_.add(prompt);
			}
			
			// restore default values
			currPromptBargein_ = true;
//			currPromptBargeintype_ = "speech";
//			currPromptTimeout_ = null;
		}
		
		// nomatch
		//<state id="@weather.place.nomatch" next="@weather.vxml_select">
		// xxx
		//</state>
		
		private void _enterNomatch() 
		{
			commands_.setupCommands();
			execContents_.setupExecContent();
			textBuf_.setupSpeak();
			
			String shadow = dialog_.getName() + "." + dialog_.getItemName() + "$";
			execContents_.appendExecContent(new EvaluateCommand("promptcond=false;",shadow));
		}
		
		public boolean enter(VxmlNomatch o) {
			// [@weather.place.nomatch]
			_enterNomatch();
			visitorStack_.push(o);
			return(true);
		}
		
		/**
		 * 
		 *
		 */
		private void _leaveNomatch()
		{
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			String nomatch = "@" + dialog_.getName() + "." + dialog_.getItemName() + ".nomatch"; 
			commands_.flushCommands(nomatch);
		}
		
		public void leave(VxmlNomatch o) {
			_leaveNomatch();
			visitorStack_.pop(); 
		}
		
		// noinput
		//<state id="@weather.place.noinput" next="@weather.vxml_select">
		// xxx
		//</state>
		
		private void _enterNoinput()
		{
			commands_.setupCommands();
			execContents_.setupExecContent();
			textBuf_.setupSpeak();
			
			String shadow = dialog_.getName() + "." + dialog_.getItemName() + "$";
			execContents_.appendExecContent(new EvaluateCommand("promptcond=false;",shadow));
		}
		
		public boolean enter(VxmlNoinput o) {
			_enterNoinput();
			visitorStack_.push(o);
			return(true);
		}
		
		private void _leaveNoinput()
		{
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			String noinput = "@" + dialog_.getName() + "." + dialog_.getItemName() + ".noinput"; 
			commands_.flushCommands(noinput);
		}
		
		public void leave(VxmlNoinput o) {
			_leaveNoinput();
			visitorStack_.pop(); 
		}
		
		// <help>
		
		private void _enterHelp()
		{
			commands_.setupCommands();
			execContents_.setupExecContent();
			textBuf_.setupSpeak();
			
			String shadow = dialog_.getName() + "." + dialog_.getItemName() + "$";
			execContents_.appendExecContent(new EvaluateCommand("condprompt=false;",shadow));
		}
		
		public boolean enter(VxmlHelp o) {
			_enterHelp();
			visitorStack_.push(o);
			return true;
		}
		
		private void _leaveHelp() {
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			String help = "@" + dialog_.getName() + "." + dialog_.getItemName() + ".vxml_help"; 
			commands_.flushCommands(help);
		}
		
		public void leave(VxmlHelp e) {
			_leaveHelp();
			visitorStack_.pop(); 
		}
		
		// TODO: event="noinput nomatch help"
		//
		public boolean enter(VxmlCatch o) {
			if (o.getEvent() == null) {
				docWarnings_.add(new DocWarning("No EVENT in <catch>"));
			}
			String event = o.getEvent();
			String events[] = event.split(" ");
			boolean hasNomatch = false, hasNoinput = false, hasHelp = false, hasError = false;
			for (int i=0; i<events.length; i++) {
				if (events[i].equals("nomatch")) {
					hasNomatch = true;
				} else if (events[i].equals("noinput")) {
					hasNoinput = true;
				} else if (events[i].equals("help")) {
					hasHelp = true;
				} else if (events[i].equals("error")) {
					hasError = true;
				}
			}
			if ( hasNomatch ) {
				_enterNomatch();
			} 
			if ( hasNoinput ) {
				_enterNoinput();
			} 
			if ( hasHelp ) {
				_enterHelp();
			} 
			if ( hasError ) {
				docWarnings_.add(new DocWarning("Not supported feature: <catch event=error>"));
			}
			
			visitorStack_.push(o);
			return true;
		}
		
		public void leave(VxmlCatch o) {
			if (visitorStack_.peek() instanceof VxmlCatch 
					&& ((VxmlCatch)(visitorStack_.peek())).getEvent() != null ) {
				String event = ((VxmlCatch)(visitorStack_.peek())).getEvent();
				if ( event.equals("nomatch") ) {
					_leaveNomatch();
				} else if ( event.equals("noinput") ) {
					_leaveNoinput();
				} else if ( event.equals("help") ) {
					_leaveHelp();
				}
			}
			visitorStack_.pop(); 
		}
		
		
		// <reprompt>
		// usage:
		//  <noinput> 聞きとれません <reprompt/> </noinput>
		//
		public boolean enter(VxmlReprompt e) {
			textBuf_.flushSpeak();
			
			String shadow = dialog_.getName() 
			+ "." + dialog_.getItemName() + "$";
			String script = "promptcond=true;\n";
			
			//String sc = _getEvalWith(script, shadow);
			execContents_.appendExecContent(new EvaluateCommand(script,shadow));
			
			textBuf_.setupSpeak();
			return true;
		}
		
		// common tags begin:
		
		public boolean enter(RString rs) {
			//String text = Util.removeSpaces(rs.getText());
			String text = rs.getText();
			dbg.print("RString: "+ text);
			textBuf_.appendString(text);
			return true;
		}
		
		public boolean enter(VxmlValue vxmlValue) {
			if (vxmlValue.getExpr() != null) {
				String expr = vxmlValue.getExpr();
				// 'xxx'+ 1+2  = xxx12  -> NG
				// 'xxx'+(1+2) = xxx3   -> OK
				textBuf_.appendValue("("+expr+")");
			}
			return true;
		}
		
		
		public boolean enter(VxmlGoto g) {
			textBuf_.flushSpeak();
			
			String n = g.getNext();
			if (n == null) {
				docErrors_.add(new DocError("no next in <goto>"));
				return false;
			}
			String next = Util.removeSpaces(n);
			if (next.startsWith("#")) {
				String eval = "'" + next.replaceAll("#", "@") + "'";
				Command c = (Command)( new GotoCommand(eval, "true") );
				execContents_.appendExecContent(c);
			} else {
				String eval = "'" + resolveAdrs(next) + "'";
				Command c = (Command)( new EndCommand(eval) );
				execContents_.appendExecContent(c);
			}
			
			textBuf_.setupSpeak();
			return true;
		}
		
		public boolean enter(VxmlExit e) {
			textBuf_.flushSpeak();
			execContents_.appendExecContent(new EndCommand(false));
			return true;
		}
		
		public boolean enter(VxmlAssign e) {
			textBuf_.flushSpeak();
			String aname = e.getName();
			String aexpr = e.getExpr();
			if ( aname == null || aexpr == null ) {
				docErrors_.add(new DocError("<assign> needs NAME and EXPR."));
				return false;
			}
			
			//String sc = aname + "=" + aexpr + ";";
			String sc = _getEvalWith(aname + "=" + aexpr, dialog_.getName());
			execContents_.appendExecContent(new EvaluateCommand(sc));
			textBuf_.setupSpeak();
			return true;
		}
		
		
		public boolean enter(VxmlScript e) {
			textBuf_.flushSpeak();
			/*
			 String text = e.getContent();
			 String sc = _getEvalWith(text, dialog_.getName());
			 execContents_.appendExecContent(new EvaluateCommand(sc));
			 */
			String text = "";
			if (e.getSrc() != null) {
				String srcpath = resolveAdrs(e.getSrc());
				try {
					text = NetUtil.loadTextFile(srcpath);
				} catch (Exception ex) {
					docErrors_.add(new DocError("<script> can't read external file. " + ex.toString()));		    
				}
			} else {
				text = e.getContent();
			}
			String sc = _getEvalWith(text, dialog_.getName());
			execContents_.appendExecContent(new EvaluateCommand(sc));
			
			textBuf_.setupSpeak();
			return true;
		}
		
		
		// if/else/elseif
		
		private String currIfCond_ = null;
		
		public boolean enter(VxmlIf e) {
			
			currIfCond_ = e.getCond();
			String cond = _getEvalWith(currIfCond_, dialog_.getName());
			execContents_.setupExecContent(cond);
			
			return true;
		}
		
		public void leave(VxmlIf e) {
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
		}
		
		// TODO: 本当は else の評価は if の実行時に行なうべき。
		public boolean enter(VxmlElse e) {
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			String cond = _getEvalWith("!(" + currIfCond_ + ")", dialog_.getName());
			execContents_.setupExecContent(cond);
			
			return true;
		}
		
		
		public boolean enter(VxmlElseif e) {
			// end of previous if
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			// begin of next if
			String c = e.getCond();
			String cond = _getEvalWith(c, dialog_.getName());
			currIfCond_ = currIfCond_ + " || " + c;
			
			execContents_.setupExecContent(cond);
			textBuf_.setupSpeak();
			return true;
		}
		
		// VoiceXML SSML
		// http://www.w3.org/TR/speech-synthesis/#S2.2.3
		//  <break time="3s"/>
		//  <break time="300ms"/>
		//  <break time="medium"/>
		//
		// TODO: large, medium などの値を可変にする
		//
		public boolean enter(VxmlBreak e) {
			String time = e.getTime();
			//String msec = "500";
			int msec = 500;
			if ( time != null ) {
				if ( time.equals("large") ) {
					msec = 2000;
				} else if ( time.equals("medium") ) {
					msec = 500;
				} else if ( time.equals("small") ) {
					msec = 200;
				} else if ( time.equals("none") ) {
					msec = 1;
				} else {
					msec = DocUtil.getCSS2TimesAsMsec(time);
				}
			}
			
			textBuf_.flushSpeak();
			execContents_.appendExecContent
			(new AddOutItemCommand(new BreakOutItem(msec, currPromptBargein_)));
			textBuf_.setupSpeak();
			
			return true;
		}
		
		// VoiceXML misc
		
		// Supported:
		// <clear namelist="city"/>
		// <clear namelist="city state zip"/>
		// <clear/> .. all form items
		public boolean enter(VxmlClear e) {
			if ( e.getNamelist() == null ) {
				if (submitItems_ == null || submitItems_.size() == 0) {
					return true;
				}
				textBuf_.flushSpeak();
				for (int i = 0; i < submitItems_.size(); i++) {
					String aname = submitItems_.getKey(i);
					String aexpr = "undefined";
					String sc = _getEvalWith(aname + "=" + aexpr, dialog_.getName());
					execContents_.appendExecContent(new EvaluateCommand(sc));
				}
				textBuf_.setupSpeak();
				return true;
				
			} else {
				textBuf_.flushSpeak();
				String names[] = e.getNamelist().split(" ");
				for (int i = 0; i < names.length; i++) {
					String aname = names[i];
					String aexpr = "undefined";
					String sc = _getEvalWith(aname + "=" + aexpr, dialog_.getName());
					execContents_.appendExecContent(new EvaluateCommand(sc));
				}
				textBuf_.setupSpeak();
				return true;
			}
		}
		
		// exit immediately
		public boolean enter(VxmlDisconnect e) {
			textBuf_.flushSpeak();
			execContents_.appendExecContent(new EndCommand(true));
			return true;
		}
		
		public boolean enter(VxmlEmphasis e) {
			if ( e.getLevel() != null ) {
				docWarnings_.add(new DocWarning("Ignored feature: <emphasis level>"));
				return false;
			}
			textBuf_.appendString("<EMPH>");
			return true;
		}
		
		public void leave(VxmlEmphasis e) {
			textBuf_.appendString("</EMPH>");
		}
		
		public boolean enter(VxmlEnumerate e) {
			docErrors_.add(new DocError("Not supported feature: <enumerate>"));
			return false;
		}
		
		// <catch event="error"> と同じ:
		// <error> エラーです。終了します。<exit/> </error>
		public boolean enter(VxmlError e) {
			docErrors_.add(new DocError("Not supported feature: <error>"));
			return false;
		}
		
		// TODO: VxmlBlock と同じように扱っている（暫定）
		public boolean enter(VxmlInitial e) {
			//docErrors_.add(new DocError("Not supported feature: <initial>"));
			//return false;
			
			if ( e.getName() == null ) {
				dialog_.setItemName(_getUniqueId("initial")); // s1
			} else {
				dialog_.setItemName(e.getName());
			}
			
			String s = "@" + dialog_.getName() + "." + dialog_.getItemName();
			String t = stateMap_.newState(s);
			// @weather.s1
			dbg.ASSERT(t.equals(s), "VxmlInitial#enter");
			
			stateMap_.addCommand(s, new EvaluateCommand
					(dialog_.getName() + "." + dialog_.getItemName() + "=true;"));
			
			visitorStack_.push(e);
			formItems_.put(dialog_.getItemName(), (Object)e);
			
			commands_.setupCommands();
			execContents_.setupExecContent();
			textBuf_.setupSpeak();
			return true;
			
		}
		
		public void leave(VxmlInitial e) {
			textBuf_.flushSpeak();
			execContents_.flushExecContent();
			
			String s = "@" + dialog_.getName() + "." + dialog_.getItemName();
			commands_.flushCommands(s);
			stateMap_.addSetNextCommand(s, "@" + dialog_.getName() + ".vxml_select");
			
			visitorStack_.pop(); 
		}
		
		
//		public boolean enter(VxmlLink e) {
//			docErrors_.add(new DocError("Not supported feature: <link>"));
//			return false;
//		}
		
		public boolean enter(VxmlLink e) {
			visitorStack_.push(e);
			return true;
		}
		
		public void leave(VxmlLink e) {
			visitorStack_.pop();
		}
		
		public boolean enter(VxmlLog e) {
			textBuf_.flushSpeak();
			textBuf_.setupLog();
			String expr = e.getExpr();
			if (expr != null) {
				textBuf_.appendValue("("+expr+")");
			}
			return true;
		}
		
		public void leave(VxmlLog e) {
			textBuf_.flushLog();
			textBuf_.setupSpeak();
		}
		
		/**
		 * <all-of>
		 *  <media>
		 *    <native>
		 *  </media>
		 *  <media>
		 *    <prompt> or <audio>
		 *  </media>
		 * </all-of>
		 */
		
		/**
		 * <prompt> or <block>  
		 * <all-of>
		 *  <media>
		 *    <native>
		 *  </media>
		 *  <media>
		 *    text or <audio>
		 *  </media>
		 * </all-of>
		 * </prompt> or </block>  
		 */
		
		private boolean isParallel_ = false;
		private int mediaCount_ = 0;
		
		public boolean enter(VxmlNative e) {
			textBuf_.flushSpeak();
			textBuf_.setupNative();
			if (visitorStack_.peek() instanceof VxmlMedia && isParallel_) {
				textBuf_.appendValue("'to @PAR set Cmd = '");
			}
			String expr = e.getExpr();
			if (expr != null) {
				textBuf_.appendValue("("+expr+")");
			}
			return true;
		}
		public void leave(VxmlNative e) {
			textBuf_.flushNative();
			textBuf_.setupSpeak();
		}
		
		public boolean enter(VxmlAllOf e) {
			textBuf_.flushSpeak();
			mediaCount_ = 0;
			visitorStack_.push(e);
			return true;
		}
		public void leave(VxmlAllOf e) {
			visitorStack_.pop();
			textBuf_.setupSpeak();
		}
		
		public boolean enter(VxmlMedia e) {
			textBuf_.flushSpeak();
			if (visitorStack_.peek() instanceof VxmlAllOf) {
				mediaCount_++;
				if (mediaCount_ == 1) {
					textBuf_.setupNative();
					textBuf_.appendValue("'to @PAR set Init = 1'");
					textBuf_.flushNative();
					isParallel_ = true;
				} else {
					textBuf_.setupSpeak();
				}
			}
			visitorStack_.push(e);
			return true;
		}
		public void leave(VxmlMedia e) {
			visitorStack_.pop();
			if (visitorStack_.peek() instanceof VxmlAllOf) {
				textBuf_.flushSpeak();
				isParallel_ = false;
			}
		}
		
		public boolean enter(VxmlMark e) {
			return true; // ignored (VXML2.0)
		}
		
		public boolean enter(VxmlMeta e) {
			docWarnings_.add(new DocWarning("Ignored feature: <meta>"));
			return true;
		}
		
		public boolean enter(VxmlObject e) {
			docWarnings_.add(new DocWarning("Ignored feature: <object>"));
			return true;
		}
		
		public boolean enter(VxmlParam e) {
			docWarnings_.add(new DocWarning("Not supported feature: <param>"));
			return true;
		}
		
		public boolean enter(VxmlPhoneme e) {
			textBuf_.appendString("<PRON SYM=\"" + e.getPh() + "\">");
			return true;
		}
		public void leave(VxmlPhoneme e) {
			textBuf_.appendString("</PRON>");
		}
		
		public boolean enter(VxmlProperty e) {
			// <vxml> <property name="bargein" value="false"/>
			// <vxml> <form> <property name="bargein" value="false"/>
			// <vxml> <menu> <property name="bargein" value="false"/>
			
			// <vxml> <form> <property name="inputmodes" value="dtmf"/>
			
			String name = e.getName();
			String value = e.getValue();
			
			Object parent = visitorStack_.peek();
			if (name.equals("bargein")) {
				if (parent instanceof VxmlMenu || parent instanceof VxmlForm) {
					dialogBargeinProperty_ = value;
				} else if (parent instanceof VxmlVxml) {
					documentBargeinProperty_ = value;
				}
			} else if (name.equals("timeout")) {
				if (parent instanceof VxmlMenu || parent instanceof VxmlForm) {
					docWarnings_.add(new DocWarning("Not supported feature: property timeout for dialog"));
				} else if (parent instanceof VxmlVxml) {
					String script = "$prompt_timeout = " + DocUtil.getCSS2TimesInMsecAsString(value); // in msec
					stateMap_.addCommand(VXML_START_NAME, new EvaluateCommand(script));
				}
			} else if (name.equals("inputmodes")) {
				docWarnings_.add(new DocWarning("Not supported feature: property inputmodes"));
			}
			return true;
		}
		
		public boolean enter(VxmlRecord e) {
			docErrors_.add(new DocError("Not supported feature: <record>"));
			return false;
		}
		
		public boolean enter(VxmlReturn e) {
			docErrors_.add(new DocError("Not supported feature: <return>"));
			return false;
		}
		
		public boolean enter(VxmlSayAs e) {
			docWarnings_.add(new DocWarning("Ignored feature: <say-as>"));
			return true;
		}
		
		public boolean enter(VxmlSubmit e) {
			String post = "";
			boolean isPost = false;
			if (e.getMethod() != null && e.getMethod().toLowerCase().equals("post") ) {
				isPost = true;
			}
			
			String script = "";
			if (e.getNext() != null) {
				script = "'" + resolveAdrs(Util.removeSpaces(e.getNext())) + "'";
			} else if (e.getExpr() != null) {
				script = Util.removeSpaces(e.getExpr());
			} else {
				docErrors_.add(new DocError("No next in <submit>"));
				return false;
			}
			
			if (isPost) {
				// do POST method
				String names[];
				String namelist = e.getNamelist();
				if (namelist == null) {
					for (int i = 0; i < submitItems_.size(); i++) {
						if ( i == 0 ) {
							//script += "+'?'";
						} else {
							post += "+'&'";
						}
						post += "'" + submitItems_.getKey(i) + "='+" + submitItems_.getKey(i);
					}
				} else {
					names = namelist.split(" ");
					for (int i = 0; i < names.length; i++) {
						if ( i == 0 ) {
							// script += "+'?'";
						} else {
							post += "+'&'";
						}
						post += "'" + names[i] + "='+" + names[i];
					}
				}
				post = _getEvalWith(post, dialog_.getName());
			} else {
				// do GET method
				String names[];
				String namelist = e.getNamelist();
				if (namelist == null) {
					for (int i = 0; i < submitItems_.size(); i++) {
						if ( i == 0 ) {
							script += "+'?'";
						} else {
							script += "+'&'";
						}
						script += "+'" + submitItems_.getKey(i) + "='+" + submitItems_.getKey(i);
					}
				} else {
					names = namelist.split(" ");
					for (int i = 0; i < names.length; i++) {
						if ( i == 0 ) {
							script += "+'?'";
						} else {
							script += "+'&'";
						}
						script += "+'" + names[i] + "='+" + names[i];
					}
				}
				script += ";";
			}
			textBuf_.flushSpeak();
			//
			String eval = _getEvalWith(script, dialog_.getName());
			Command c = (Command)( new EndCommand(eval, "true", false, post) );
			execContents_.appendExecContent(c);
			//
			textBuf_.setupSpeak();
			return true;
		}
		
		
		public boolean enter(VxmlThrow e) {
			docErrors_.add(new DocError("Not supported feature: <throw>"));
			return false;
		}
		
		public boolean enter(VxmlTransfer e) {
			docWarnings_.add(new DocWarning("Not supported feature: <transfer>"));
			
			textBuf_.flushSpeak();
			execContents_.appendExecContent(new EndCommand(false));
			return true;
		}
		
		public boolean enter(VxmlVoice e) {
			String optional = "male01"; // TODO: 
			if (e.getName() != null) {
				optional = e.getName();
			} else {
				if (e.getGender() != null) {
					if ( e.getGender().equals("male")) {
						optional = "male01";
					} else if ( e.getGender().equals("female")) {
						optional = "female01";
					}
				}
			}
			textBuf_.appendString("<VOICE OPTIONAL=\"" + optional + "\">");
			return true;
		}
		
		public void leave(VxmlVoice e) {
			textBuf_.appendString("</VOICE>");
		}
		
		
		// VoiceXML prosody (macro)
		
		private String leaveProsodyTag_ = null;
		
		public boolean enter(VxmlProsody e) {
			String enterProsodyTag = "";
			leaveProsodyTag_ = "";
			
			if (e.getPitch() != null) {
				if (e.getPitch().equals("x-high")) {
					enterProsodyTag += "<PITCH LEVEL=\"140\">";
				} else if (e.getPitch().equals("high")) {
					enterProsodyTag += "<PITCH LEVEL=\"120\">";
				} else if (e.getPitch().equals("medium")) {
					enterProsodyTag += "<PITCH LEVEL=\"100\">";
				} else if (e.getPitch().equals("low")) {
					enterProsodyTag += "<PITCH LEVEL=\"80\">";
				} else if (e.getPitch().equals("x-low")) {
					enterProsodyTag += "<PITCH LEVEL=\"60\">";
				} else if (e.getPitch().equals("default")) {
					enterProsodyTag += "<PITCH LEVEL=\"100\">";
				}
				leaveProsodyTag_ = "</PITCH>" + leaveProsodyTag_;
			}
			
			if (e.getRange() != null) {
				if (e.getRange().equals("x-high")) {
					enterProsodyTag += "<PITCH RANGE=\"140\">";
				} else if (e.getRange().equals("high")) {
					enterProsodyTag += "<PITCH RANGE=\"120\">";
				} else if (e.getRange().equals("medium")) {
					enterProsodyTag += "<PITCH RANGE=\"100\">";
				} else if (e.getRange().equals("low")) {
					enterProsodyTag += "<PITCH RANGE=\"80\">";
				} else if (e.getRange().equals("x-low")) {
					enterProsodyTag += "<PITCH RANGE=\"60\">";
				} else if (e.getRange().equals("default")) {
					enterProsodyTag += "<PITCH RANGE=\"100\">";
				}
				leaveProsodyTag_ = "</PITCH>" + leaveProsodyTag_;
			}
			
			if (e.getVolume() != null) {
				if (e.getRange().equals("silent")) {
					enterProsodyTag += "<VOLUME LEVEL=\"0\">";
				} else if (e.getRange().equals("x-soft")) {
					enterProsodyTag += "<VOLUME LEVEL=\"60\">";
				} else if (e.getRange().equals("soft")) {
					enterProsodyTag += "<VOLUME LEVEL=\"80\">";
				} else if (e.getRange().equals("medium")) {
					enterProsodyTag += "<VOLUME LEVEL=\"100\">";
				} else if (e.getRange().equals("loud")) {
					enterProsodyTag += "<VOLUME LEVEL=\"120\">";
				} else if (e.getRange().equals("x-loud")) {
					enterProsodyTag += "<VOLUME LEVEL=\"140\">";
				} else if (e.getRange().equals("default")) {
					enterProsodyTag += "<VOLUME LEVEL=\"100\">";
				}
				leaveProsodyTag_ = "</VOLUME>" + leaveProsodyTag_;
			}
			
			textBuf_.appendString(enterProsodyTag);
			return true;
		}
		public void leave(VxmlProsody e) {
			dbg.ASSERT(leaveProsodyTag_ != null, "leave VxmlProsody");
			textBuf_.appendString(leaveProsodyTag_);
			leaveProsodyTag_ = null;
		}
		
		// VoiceXML paragraph
		
		public boolean enter(VxmlP e) {
			textBuf_.setupSpeak();
			return true;
		}
		public void leave(VxmlP e) {
			textBuf_.appendString("<SILENCE MSEC=\"500\"/>");
			textBuf_.flushSpeak();
		}
		
		public boolean enter(VxmlParagraph e) {
			textBuf_.setupSpeak();
			return true;
		}
		public void leave(VxmlParagraph e) {
			textBuf_.appendString("<SILENCE MSEC=\"500\"/>");
			textBuf_.flushSpeak();
		}
		
		// VoiceXML sentence
		
		public boolean enter(VxmlS e) {
			textBuf_.setupSpeak();
			return true;
		}
		public void leave(VxmlS e) {
			textBuf_.flushSpeak();
		}
		
		public boolean enter(VxmlSentence e) {
			textBuf_.setupSpeak();
			return true;
		}
		public void leave(VxmlSentence e) {
			textBuf_.flushSpeak();
		}
		
		// Galatea extention tags
		
		public boolean enter(VxmlEMPH e) {
			textBuf_.appendString("<EMPH>");
			return true;
		}
		public void leave(VxmlEMPH e) {
			textBuf_.appendString("</EMPH>");
		}
		
		// SILENCE
		
		public boolean enter(VxmlSILENCE e) {
			textBuf_.appendString("<SILENCE MSEC=\"" + e.getMSEC() + "\"/>");
			return true;
		}
		
		// PRON
		
		public boolean enter(VxmlPRON e) {
			textBuf_.appendString("<PRON SYM=\"" + e.getSYM() + "\">");
			return true;
		}
		
		public void leave(VxmlPRON e) {
			textBuf_.appendString("</PRON>");
		}
		
		// VOICE
		
		public boolean enter(VxmlJVOICE e) {
			String optional = e.getOPTIONAL();
			if ( optional == null ) {
				if ( e.getREQUIRED() != null ) {
					if ( e.getREQUIRED().equals("FEMALE") 
							|| e.getREQUIRED().equals("female") ) {
						optional = "female01";
					} else {
						optional = "male01";
					}
				}
			}
			
			String str = "<VOICE";
			if ( optional != null ) {
				str += " OPTIONAL=\"" + optional + "\"";
			}
			String alpha = e.getALPHA();
			if ( alpha != null ) {
				str += " ALPHA=\"" + alpha + "\"";
			}
			str +=">";
			textBuf_.appendString(str);
			
			return true;
		}
		
		public void leave(VxmlJVOICE e) {
			textBuf_.appendString("</VOICE>");
		}
		
		// RATE
		
		public boolean enter(VxmlRATE e) {
			textBuf_.appendString("<RATE SPEED=\"" + e.getSPEED() + "\">");
			return true;
		}
		
		public void leave(VxmlRATE e) {
			textBuf_.appendString("</RATE>");
		}
		
		// VOLUME
		
		public boolean enter(VxmlVOLUME e) {
			textBuf_.appendString("<VOLUME LEVEL=\"" + e.getLEVEL() + "\">");
			return true;
		}
		
		public void leave(VxmlVOLUME e) {
			textBuf_.appendString("</VOLUME>");
		}
		
		// PITCH
		
		public boolean enter(VxmlPITCH e) {
			String att = "";
			if ( e.getLEVEL() != null ) {
				att += " LEVEL=\"" + e.getLEVEL() + "\"";
			}
			if ( e.getRANGE() != null ) {
				att += " RANGE=\"" + e.getRANGE() + "\"";
			}
			textBuf_.appendString("<PITCH" + att+ ">");
			return true;
		}
		
		public void leave(VxmlPITCH e) {
			textBuf_.appendString("</PITCH>");
		}
		
		// SPELL
		
		public boolean enter(VxmlSPELL e) {
			textBuf_.appendString("<SPELL>");
			return true;
		}
		
		public void leave(VxmlSPELL e) {
			textBuf_.appendString("</SPELL>");
		}
		
		// CONTEXT
		
		public boolean enter(VxmlCONTEXT e) {
			String att = "";
			if ( e.getTYPE() != null ) {
				att += " TYPE=\"" + e.getTYPE() + "\"";
			}
			if ( e.getTYPE().equals("NUMBER") ) {
				if ( e.getFORMAT() != null ) {
					att += " FORMAT=\"" + e.getFORMAT() + "\"";
				}
			}
			if ( e.getTYPE().equals("DATE") ) {
				if ( e.getFORMAT() != null ) {
					att += " FORMAT=\"" + e.getFORMAT() + "\"";
				}
				if ( e.getDELIM() != null ) {
					att += " DELIM=\"" + e.getDELIM() + "\"";
				}
			}
			textBuf_.appendString("<CONTEXT"+att+">");
			return true;
		}
		
		public void leave(VxmlCONTEXT e) {
			textBuf_.appendString("</CONTEXT>");
		}
		
		// AUDIO
		
		public boolean enter(VxmlAudio e) {
			textBuf_.flushSpeak();
			String src = e.getSrc();
			if ( src == null ) {
				docWarnings_.add(new DocWarning("Error: <audio src> missing."));
				return true;
			}
			//String fileRel = resolveAdrs(src);
			//String userdir = getDocAddress(); //System.getProperty("user.dir") + "/" ;
			//String file = Util.resolveAdrs(userdir, fileRel);
			//dbg.print("enter(VxmlAudio) " + userdir + " " + fileRel + " " + file);
			//file = "'" + file + "'";
			String file = "'" + resolveAdrs(src) + "'";
			execContents_.appendExecContent
			(new AddOutItemCommand(new AudioOutItem(file, currPromptBargein_)));
			// do not speak inside <audio>
			// textBuf_.setupSpeak();
			return true;
		}
		
		public void leave(VxmlAudio e) {
			// do not speak inside <audio>
			// textBuf_.flushSpeak();
			textBuf_.setupSpeak();
		}
		
		
		// EMOTION
		
		public boolean enter(VxmlEmotion e) {
			textBuf_.flushSpeak();
			String typename = e.getType();
			int value = 100;
			int duration = 0;
//			int pattern = 1;
			if ( e.checkPattern() ) {
				value = e.getPattern();
			}
			if ( e.checkValue() ) {
				value = e.getValue();
			}
			if ( e.checkDuration() ) {
				duration = e.getDuration();
			}
			if ( typename == null ) {
				typename = "NEUTRAL";
			}
			typename = typename.toUpperCase();
			
			String text = "'to @AM-MCL set Emotion = " + typename + " "  + value + " " + duration + "'";
			NativeOutItem item = new NativeOutItem(text, dialog_.getName());
			execContents_.appendExecContent(new AddOutItemCommand(item));
			
			textBuf_.setupSpeak();
			return true;
		}
		
		public void leave(VxmlEmotion emotion) {
			textBuf_.flushSpeak();
			//
			// TODO: set previous values
			//
			textBuf_.setupSpeak();
		}
		
		// common tags end
		
	}
	
	/**
	 * 
	 * @param vxmlVxml
	 * @param stateMap
	 * @param dialog
	 * @throws Exception
	 */
	private void _addAppInitStates(VxmlVxml vxmlVxml, StateMap stateMap, String dialog) 
	throws Exception
	{
		String app = vxmlVxml.getApplication();
		String appInitScript = 
			"document = {};" +
			"dialog = {};";
		if (app == null) {
			appInitScript += 
				"session={telephone:{ani:\"0\"}};" 
				+ "application={lastresult$:{}};";
			
		} else {
			
			appInitScript += 
				"var $root='" + docAddress_ + "';"
				+ "if ($current_root==undefined) {"
				+ "  var $current_root;" 
				+ "}" 
				+ "if ($current_root!=$root) {" 
				+ "  $current_root=$root;" 
				+ "  session={};"
				+ "  application={};"
				+ "  application.lastresult$={};";
			
			String applicationRoot = resolveAdrs(app);
			dbg.print("rootVxml:" + applicationRoot );
			
			VxmlVxml rootVxml = new VxmlVxml(applicationRoot);
			
			dbg.print("Parsing applicationRoot...",2 );
			int nContent = rootVxml.sizeContent();
			for ( int i = 0; i < nContent; i++ ) {
				IVxmlVxmlChoice choice = rootVxml.getContent(i);
				
				if ( choice instanceof VxmlVar ) {
					VxmlVar var = (VxmlVar)choice;
					String name = var.getName();
					String expr = var.getExpr();
					String command = "application."+name+"="+expr+";";
					
					appInitScript += command;
					
				} else if ( choice instanceof VxmlLink ) {
					VxmlLink link = (VxmlLink)choice;
					String n = link.getNext();
					// next は appRoot からの相対アドレスなので正規化が必要
					String next = Util.resolveAdrs(applicationRoot, n);
					dbg.print("link next="+next );
				}
			}
			appInitScript += "}";
		}
		
		ContentState appInitState = new ContentState();
		String docName = VXML_START_NAME; // "@vxml_start"
		appInitState.setName(docName);
		if ( appInitScript.length() > 0 ) {
			appInitState.addCommand(new EvaluateCommand(appInitScript));
		}
		
		String firstID = dialog;
		if (firstID == null || firstID.equals("")) {
			firstID = DocUtil.getFirstDialogID(vxmlVxml);
			if(firstID == null) {
				throw new DocError("VXML leaf document " +docAddress_+ " has no menu or form.");
			}
		}
		
		stateMap.add(appInitState);
		stateMap.setFirstStateName(docName);
		stateMap.addSetNextCommand(docName, "@" + firstID);
		
		// stateMap.addCommand(docName, new AddOutItemCommand
		// (new BreakOutItem(startupWait_ * 1000.0, false)));
		
		String script = "$prompt_timeout = "+Double.toString(promptTimeout_ * 1000.0); // in msec
		stateMap.addCommand(docName, new EvaluateCommand(script));
		
		stateMap.addCommand(docName, new EvaluateCommand
				("$restart_document='"+docAddress_ +"';"));
	}
	
	
	/**
	 * @see main() for usage
	 */
	public VXMLDoc(Element e, String adrs, String dialog) throws Exception
	{
		promptTimeout_ = Property.getAsDouble("VXMLDoc.PromptTimeout", 10.0);
		
		docErrors_ = new ArrayList<DocError>();
		docWarnings_ = new ArrayList<DocWarning>();
		
		if (VxmlVxml.isMatch(e) == false) {
			throw new DocError("Not a valid <vxml> document: " + adrs + "#" + dialog);
		}
		
		docAddress_ = adrs + "#" + dialog;
		
		stateMap_ = new StateMap();
		
		VxmlVxml vxmlVxml = new VxmlVxml(e);
		_addAppInitStates(vxmlVxml, stateMap_, dialog);
		
		VXMLDoc.Visitor visitor = new VXMLDoc.Visitor();
		URVisitor.traverse(vxmlVxml, visitor);
		
		if ( docErrors_.size() > 0 ) {
			String err = "";
			for ( int i = 0; i < docErrors_.size(); i++ ) {
				err += docErrors_.get(i).toString() + "\n";
			}
			throw new DocError("Leaf document has following error(s):\n" + err);
		}
		
	}
	
	public StateMap getStateMap()
	{
		return stateMap_;
	}
	
	public String getDocWarnings() {
		String w = "";
		for ( int i = 0, n = docWarnings_.size(); i < n ; i++ ) {
			w += docWarnings_.get(i) + "\n";
		}
		return w;
	}
	
}
