package com.ftinc.si.assist.run;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import com.ftinc.si.assist.test.AssertRecord;
import com.ftinc.si.assist.test.CodeProcessor;
import com.ftinc.si.assist.test.DBService;
import com.ftinc.si.assist.test.Fson;
import com.ftinc.si.assist.test.TestLogger;
import com.ftinc.si.assist.test.Tool;

public class VCentral {
	private static HashMap<String, Object> _map = null;
	private static int cur_cmd4Assertables = 0;

	//TestCase終了時にクリアする。
	public static void clear() {
		cur_cmd4Assertables = 0;
		_map = null;

		//DBを閉じる。
		for(Entry<String, DBService> entry :_dbs.entrySet()) {
			entry.getValue().destroy();
		}

		_tblMap = new HashMap<String, String>();
		_dbs = new HashMap<String, DBService>();
	}

	//_mapを引数のマップで重複したキーのみを上書きする。
	//＿ASSERTABLEや＿POST_EXECSについては、新規コマンドIDごとに自動的に初期化する。
	@SuppressWarnings("unchecked")
	public static void prepare(HashMap<String, Object> map) {
		if (_map == null) {
			_map = new HashMap<String, Object>();
		}

		//Assert情報格納用のリセット：「//?」の後に、dbtest, objtestがある場合、後で判定するために別管理しておく。
		//TestCommandRecordの内部クラスAssertMacroRecordで使用する。
		//★★★注意!!★★★　一つのメソッドに対して一つの引数にだけ大域変数を設定してよい。複数はNG.
		if (Tool.curID != cur_cmd4Assertables) {
			//現在のコマンドIDで初めて呼ばれたので更新する。
			cur_cmd4Assertables = Tool.curID;

			//以下は初期化
			if (_map.containsKey("_ASSERTABLES")) { //$NON-NLS-1$
				_map.remove("_ASSERTABLES");	//判定条件を削除 //$NON-NLS-1$
			}
			if (_map.containsKey("_POST_EXECS")) { //$NON-NLS-1$
				_map.remove("_POST_EXECS");		//テスト実行後の外部プロセスを削除 //$NON-NLS-1$
			}
		}

		if (map != null) {
			//beforeとdbaddは先に来る。
			for(Entry<String, Object> entry : map.entrySet()) {
				//書式(getKey):		特殊コマンド　キー
				//特殊コマンド：dbadd,dbset,dbtest,objset,objtest,exec
				//キー：$.で始まる。JsonPath。外部プロセス起動の場合は、boforeもしくはafter

				String t_key = entry.getKey();//特殊コマンド＋キー文字列。

				//コマンド文字列を判別して処理する。
				if (t_key.startsWith("dbadd ")) { //$NON-NLS-1$
					//★★★★重要★★★★
					//JDBC接続情報をもとにDBと接続し、コネクションを保持する。登録のみである。
					//dbsetやdbtestとは同一コマンドには同居できない。それ以前に行うべきである。
					t_key = t_key.replaceFirst("dbadd\\s*", ""); //$NON-NLS-1$ //$NON-NLS-2$

					if (!(entry.getValue() instanceof List)) {
						Tool.alertAndExit("Failed to cast to List from : " + entry.getValue().toString(), null, Tool.curID); //$NON-NLS-1$
					}
					addJDBC(t_key, (ArrayList<String>)entry.getValue());
				} else if (t_key.startsWith("exec before")) { //$NON-NLS-1$
					//★★★★重要★★★★
					//必要に応じて別コマンドで行うべきである。DBSETと混在すると順序が分からなくなる。
					ArrayList<String> t_list = (ArrayList<String>)entry.getValue();
					String t_path = t_list.get(0);
					t_list.remove(0);
					String[] t_cmds = t_list.toArray(new String[t_list.size()]);
					if (!t_cmds[0].contains("\\")) {
						//ProcessBuilderの実行フォルダを指定しても、exeが見つからない場合があるため、念のためフルパスにする。
						t_cmds[0] = t_path + "\\" + t_cmds[0];
					}

					int t_res = Tool.executeCommand(new StringBuilder(), t_path, t_cmds);
					if (t_res > 0) {
						//何かおかしい。beforeなのに失敗。
						Tool.alertAndExit(t_path + Messages.getString("VCentral.8") + Integer.toString(t_res), null, Tool.curID); //$NON-NLS-1$
					}
				}
			}

			//本体の実行
			for(Entry<String, Object> entry : map.entrySet()) {
				String t_key = entry.getKey();//特殊コマンド＋キー文字列。
				if (t_key.startsWith("dbset")) { //$NON-NLS-1$
					if (!(entry.getValue() instanceof Map)) {
						Tool.alertAndExit("Failed to cast to Map from : " + entry.getValue().toString(), null, Tool.curID); //$NON-NLS-1$
					}
					//JsonPathによりDBを更新する。_mapには残さない。
					updateByJsonPath(t_key, ((HashMap<String, String>)entry.getValue()));
				} else if (t_key.matches("^\\w+test\\s.*$")) { //objtest, dbtestなど //$NON-NLS-1$
					if (!(entry.getValue() instanceof String)) {
						Tool.alertAndExit("Failed to cast to String from : " + entry.getValue().toString(), null, Tool.curID); //$NON-NLS-1$
					}
					//成否判定する条件の場合、testが含まれる。
					if (!_map.containsKey("_ASSERTABLES")) { //$NON-NLS-1$
						_map.put("_ASSERTABLES", new HashMap<String, String>()); //$NON-NLS-1$
					}

					//判定用のキー（table＋where相当のカラムパスもしくは属性パス）、値（判定用正規表現）をASSERTABLESに格納する。
					HashMap<String, String> t_map = (HashMap<String, String>)_map.get("_ASSERTABLES"); //$NON-NLS-1$
					t_map.put(t_key, (String)map.get(t_key));
				} else if (t_key.startsWith("exec after")) { //$NON-NLS-1$
					if (!(entry.getValue() instanceof List)) {
						Tool.alertAndExit("Failed to cast to List from : " + entry.getValue().toString(), null, Tool.curID); //$NON-NLS-1$
					}
					//afterは抽出して、後で実行する。
					if (!_map.containsKey("_POST_EXECS")) { //$NON-NLS-1$
						_map.put("_POST_EXECS", new HashMap<String, String>()); //$NON-NLS-1$
					}
					//外部起動用のパラメータを_POST_EXECSに格納する。
					HashMap<String, String> t_map = (HashMap<String, String>)_map.get("__POST_EXECS"); //$NON-NLS-1$

					ArrayList<String> t_list = (ArrayList<String>)entry.getValue();
					String t_path = t_list.get(0);
					t_list.remove(0);
					String[] t_cmds = t_list.toArray(new String[t_list.size()]);

					if (!t_cmds[0].contains("\\")) {
						//ProcessBuilderの実行フォルダを指定しても、exeが見つからない場合があるため、念のためフルパスにする。
						t_cmds[0] = t_path + "\\" + t_cmds[0];
					}

					t_map.put(t_path, Tool.getJSONfromObject(t_cmds));
				} else {
					//上記以外は大域変数として永続的に保持する。
					_map.put(t_key, entry.getValue());
				}
			}
		}
	}

	//現在の_mapを可能な限りJsonにする。snapshotのため。TestCommandRecord.getTObjectから呼ばれる。
	public static String getJson() {
		return Tool.getJSONfromObject(_map);
	}

	//////////////////////////////////////////////////////////////////////////////
	//DataBaseアクセス用
	private static HashMap<String, String> _tblMap = new HashMap<String, String>();
	private static HashMap<String, DBService> _dbs = new HashMap<String, DBService>();

	//jdbcAdd [?(@.jdbc='接続文字列')][?(@.uid='ログインID')][?(@.pwd='パスワード')]
	private static final Pattern jdbc_info_pat = Pattern.compile("\\[\\?\\(@\\.(\\w+)='([^'\"\\]]+)'\\)\\]"); //$NON-NLS-1$

	//JDBC情報であるなら、DBServideを作りとテーブルとの紐づけを行う。
	//第一引数は$.から始まる。
	private static void addJDBC(String jdbc_info, ArrayList<String> tables) {
		String con_str = ""; //$NON-NLS-1$
		String uid = ""; //$NON-NLS-1$
		String pwd = ""; //$NON-NLS-1$
		Matcher t_m = jdbc_info_pat.matcher(jdbc_info);
		while (t_m.find()) {
			String t_key = t_m.group(1);
			String t_val = t_m.group(2);
			switch (t_key) {
				case "jdbc": con_str = t_val; break; //$NON-NLS-1$
				case "uid": uid = t_val; break; //$NON-NLS-1$
				case "pwd": pwd = t_val; break; //$NON-NLS-1$
			}
		}
		for (int i = 0; i < tables.size(); i++) {
			addTable(tables.get(i), con_str, uid, pwd);
		}
	}


	//DBServiceを必要に応じて追加する。二度書き禁止。
	private static void addTable(String tableName, String jdbsStr, String uid, String pwd) {
		if (tableName.matches("^[^\\.]+\\.[^\\.]+$")) {
			tableName = tableName.replaceFirst("^([^\\.]+)\\.([^\\.]+)$", "$1" + Matcher.quoteReplacement(".") + "$2");
		}
		if (!_tblMap.containsKey(tableName)) {
			_tblMap.put(tableName, jdbsStr);
			if (!_dbs.containsKey(jdbsStr)) {
				DBService ds = new DBService(true, true);
				if (ds.init(jdbsStr, uid, pwd)) {
					_dbs.put(jdbsStr, ds);
				} else {

				}
			}
		}
	}

	//SQLの特殊文字（登録時）をエスケープ
	protected static String esq(String str) {
		if (str != null) {
			String t_str = Tool.replaceB(str, "'", "''"); //$NON-NLS-1$ //$NON-NLS-2$
			return t_str;
		}
		return ""; //$NON-NLS-1$
	}

	//$.dbset.テーブル名[....]   ※...は、条件が複数の場合に][を複数含む。
//	private static final Pattern db_set_pat = Pattern.compile("^\"?([^\\[\\]]+?)\"?\\[(.+)\\]$"); //$NON-NLS-1$

	//テーブルを更新する。
	//key：$.テーブル名[行を特定するための条件] (JsonPathで記述)
	//vals：セットするカラム名とカラム値の組を指定。
	private static void updateByJsonPath(String key, HashMap<String, String> vals) {
		//第二引数は、設定すべきカラム名、カラム値のmapである。
		String t_key = key.replaceFirst("dbset\\s*\"?([^\"]+)\"?", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
		if (t_key.toLowerCase().startsWith("SQL_")) {
			String xsql = vals.get("@sql");
			String tblName = null;
			if (xsql.toLowerCase().matches("insert\\s+into\\s+([^\\s\\(]+)[\\s\\S]*$")) {
				tblName = xsql.replaceFirst("(?i)insert\\s+into\\s+([^\\s\\(]+)[\\s\\S]*$", "$1");
			} else if (xsql.toLowerCase().matches("update\\s+([^\\s]+)\\s+set[\\s\\S]*$")) {
				tblName = xsql.replaceFirst("(?i)update\\s+([^\\s]+)\\s+set[\\s\\S]*$", "$1");
			} else if (xsql.toLowerCase().matches("delete\\s+from\\s+([^\\s\\(]+)[\\s\\S]*$")) {
					tblName = xsql.replaceFirst("(?i)delete\\s+from\\s+([^\\s\\(]+)[\\s\\S]*$", "$1");
			}
			try {
				_dbs.get(_tblMap.get(tblName)).updateRecordBySQL(xsql, true);
			} catch (SQLException e) {
				e.printStackTrace();
			}
			return;
		}
/*
		Matcher t_m = db_set_pat.matcher(t_key);
		if (t_m.find()) {
			String tableName = t_m.group(1);
			if (tableName.matches("^[^\\.]+\\.[^\\.]+$")) {
				tableName = tableName.replaceFirst("^([^\\.]+)\\.([^\\.]+)$", "$1" + Matcher.quoteReplacement(".") + "\"$2\"");
			} else {
				tableName = "\"" + tableName + "\"";
			}

			if (_tblMap.containsKey(tableName)) {
				String[] t_conds = t_m.group(2).split("\\]\\[");//　][ごとに条件があるので分離する。 //$NON-NLS-1$
				for (int i = 0; i < t_conds.length; i++) {
					if (t_conds[i].matches("^\\?\\(@\\..*\\)$")) { //[]の中は、?(@.カラム名 論理演算子 値) //$NON-NLS-1$
						//JsonPathの評価式パターンを抽出。評価式からwhere句を作る。
						t_conds[i] = t_conds[i].replaceFirst("\\s*" + Pattern.quote("?(@."), "\""); //$NON-NLS-1$ //$NON-NLS-2$
						t_conds[i] = t_conds[i].replaceFirst("\\s*([<>=]+|like)\\s*['\"]?(.+?)['\"]?\\s*\\)\\s*$", "\"" + "$1'$2'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					} else {
						TestLogger.err(t_conds[i] + Messages.getString("VCentral.42")); //$NON-NLS-1$
						return;
					}
				}

				//select *　にして、条件にあう行の存在を確認する。0もしくは複数
				t_key += ".*"; //$NON-NLS-1$
				String t_list = selectByJsonPath(t_key);

				if (t_list == null) {
					//該当行が存在しないので新規。insert
					String t_sql = "insert into " + tableName + " "; //$NON-NLS-1$ //$NON-NLS-2$
					String str_cols = "("; //$NON-NLS-1$
					String str_vals = "values ("; //$NON-NLS-1$
					for (int j = 0; j < t_conds.length; j++) {
						if (j > 0) {
							str_cols += ","; //$NON-NLS-1$
							str_vals += ","; //$NON-NLS-1$
						}
						String[] temp = t_conds[j].split("[<>=]+|like"); //$NON-NLS-1$
						str_cols += temp[0];
						str_vals += esq(temp[1]);
					}
					for(Entry<String, String> entry : vals.entrySet()) {
						str_cols += ",\"" + entry.getKey() + "\""; //$NON-NLS-1$ //$NON-NLS-2$
						str_vals += ",'" + esq(entry.getValue().toString()) + "'"; //$NON-NLS-1$ //$NON-NLS-2$
					}
					str_cols += ")"; //$NON-NLS-1$
					str_vals += ")"; //$NON-NLS-1$

					try {
						//_tblMapからテーブルに対応する接続文字列を得る。接続文字列をキーにコネクションを得て、SQLを実行する。
						_dbs.get(_tblMap.get(tableName)).updateRecordBySQL(t_sql + str_cols + str_vals, true);
					} catch (SQLException e) {
						e.printStackTrace();
					}
				} else {
					//該当行が存在するので更新。update
					String t_sql = "update \"" + tableName + "\" set "; //$NON-NLS-1$ //$NON-NLS-2$
					boolean b_add = false;//カンマ挿入可否フラグ
					for(Entry<String, String> entry : vals.entrySet()) {
						if (b_add) {
							t_sql += ","; //$NON-NLS-1$
						}
						b_add = true;
						t_sql += "\"" + entry.getKey() + "\"='" + esq(entry.getValue().toString()) + "'"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}

					String str_where = " "; //$NON-NLS-1$
					for (int i = 0; i < t_conds.length; i++) {
						if (i > 0) {
							str_where += " and "; //$NON-NLS-1$
						} else {
							str_where += "where "; //$NON-NLS-1$
						}
						str_where += t_conds[i];
					}

					try {
						_dbs.get(_tblMap.get(tableName)).updateRecordBySQL(t_sql + str_where, true);
					} catch (SQLException e) {
						TestLogger.err(e.getMessage());
					}
				}
			}
		} else {
			TestLogger.err("Parse Error:" + t_key + Messages.getString("VCentral.69")); //$NON-NLS-1$ //$NON-NLS-2$
		}
*/
	}

//	private static final Pattern db_test_pat = Pattern.compile("\"?([^\\[]+?)\"?\\[(.+)\\]\\.([^\\[\\]]+)\\s*"); //$NON-NLS-1$

	//JsonPathによりテーブルから情報を取得する。
	//key：テーブル名[....][カラム名]　　※...は][を含む。where句を形成する。末尾のカラム名は取得するカラムを指定
	//返却値：複数ヒットする場合は、文字列を返す。
	//        カラムが*の場合     ：  カラム名:カラム値\t....\n...  //\nは複数レコードがヒットした場合
	//        カラム名が指定の場合：  カラム値\n....
	//        count(*)の場合      :   数字
	//　　　　ヒットする行が1つである、かつ、カラムが指定されている場合には、カラム値を文字列で返す。
	private static String selectByJsonPath(String key) {
		//JsonPathによるDBアクセス
		String varkey = key.replaceFirst("dbtest\\s*\"?([^\"]*)\"?", "$1");//JsonPathのみ残す。 //$NON-NLS-1$ //$NON-NLS-2$

		if (varkey.toLowerCase().startsWith("sql_")) {
			@SuppressWarnings("unchecked")
			HashMap<String, String> t_map = (HashMap<String, String>)_map.get("_ASSERTABLES"); //$NON-NLS-1$
			ArrayList<HashMap<String, String>> t_result;
			try {
				String tblName = t_map.get(key).replaceFirst("(?i)^.*?from\\s+\"?([^\"\\s]+).*?$", "$1");
				String xjdbc = _tblMap.get(tblName);
				t_result = _dbs.get(xjdbc).selectBySQL(t_map.get(key));
				if (t_result.size() > 0) {
					return "true";
				}
			} catch (Exception e) {
				TestLogger.err("Pipe Error:" + key + " execution no response.");
				return null;
			}
			return "false";
		}
		return null;
/*
		Matcher t_m = db_test_pat.matcher(varkey);
		if (t_m.find()) {
			String tableName = t_m.group(1);
			if (tableName.matches("^[^\\.]+\\.[^\\.]+$")) {
				tableName = tableName.replaceFirst("^([^\\.]+)\\.([^\\.]+)$", "$1" + Matcher.quoteReplacement(".") + "\"$2\"");
			} else {
				tableName = "\"" + tableName + "\"";
			}
			String selectedCol = t_m.group(3).replaceFirst("^[\"']?(.+?)[\"']?$", "$1");

			if (_tblMap.containsKey(tableName)) {
				String[] t_conds = t_m.group(2).split("\\]\\["); //$NON-NLS-1$
				String str_where = " "; //$NON-NLS-1$
				for (int i = 0; i < t_conds.length; i++) {
					if (i > 0) {
						str_where += " and "; //$NON-NLS-1$
					} else {
						str_where += "where "; //$NON-NLS-1$
					}
					if (t_conds[i].matches("^\\s*\\?\\(@\\..*\\s*\\)\\s*$")) { //$NON-NLS-1$
						//JsonPathの評価式パターンを抽出。評価式からwhere句を作る。?(@.カラム名 論理式)
						t_conds[i] = t_conds[i].replaceFirst("^\\s*" + Pattern.quote("?(@."), "\""); //$NON-NLS-1$ //$NON-NLS-2$
						t_conds[i] = t_conds[i].replaceFirst("\\s*([<>=]+|like)\\s*['\"]?(.+?)['\"]?\\s*\\)\\s*", "\"" + "$1'$2'"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					} else {
						TestLogger.err(t_conds[i] + Messages.getString("VCentral.85")); //$NON-NLS-1$
						return null;
					}
					str_where += t_conds[i];
				}

				//count(*)以外なら、引用符で囲む。
				if (!"count(*)".equals(selectedCol)) { //$NON-NLS-1$
					selectedCol = "\"" + selectedCol + "\""; //$NON-NLS-1$ //$NON-NLS-2$
				}
				String t_sql = "select " + selectedCol + " from " + tableName + " " + str_where; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				ArrayList<HashMap<String, String>> t_result;
				try {
					t_result = _dbs.get(_tblMap.get(tableName)).selectBySQL(t_sql);
					if (t_result != null && t_result.size() > 0) {
						String str_result = ""; //$NON-NLS-1$
						if ("count(*)".equals(selectedCol)) { //$NON-NLS-1$
							//count(*)ならListの長さは１で、Mapのキーは「count」
							str_result = t_result.get(0).get("count"); //$NON-NLS-1$
						} else {
							for (int i = 0; i < t_result.size(); i++) {
								HashMap<String, String> t_map = t_result.get(i);
								if (i > 0) {
									str_result += "\n"; //$NON-NLS-1$
								}
								int t_i = 0;
								for (Entry<String, String> entry: t_map.entrySet()) {
									if (t_i > 0) {
										str_result += "\t";
									}
									if (selectedCol.equals("*")) {
										str_result += entry.getKey() + ":" + entry.getValue().toString() + "";
									} else if (entry.getKey().equals(selectedCol)) {
										str_result += entry.getValue().toString();
									}
									t_i++;
								}
							}
						}
						return str_result;
					}
				} catch (Exception e) {
					TestLogger.err("Pipe Error:" + key + " execution no response.");
					return null;
				}
			}
		}
		TestLogger.err("Parse Error:" + key + Messages.getString("VCentral.99")); //$NON-NLS-1$ //$NON-NLS-2$
		return null;
*/
	}


	//////////////////////////////////////////////////////////////////////////////
	//引数がクラス名のとき、Fakeを取得。なければ生成。
	//testid$[_0-9]+のとき、インスタンスを取得。
	public static Object getFakeObject(String... vals) {
		if (vals[0].endsWith("[]")) { //$NON-NLS-1$
			//配列のFakeはできない。Fakeを作ってから配列に入れるならOK。
			TestLogger.err("VCentral#getTObject>>" + vals[0] + Messages.getString("VCentral.102")); //$NON-NLS-1$ //$NON-NLS-2$
			return null;
		}
		return Tool.getFakeInstance(vals);
	}


	//辞書登録されたオブジェクトにアクセスする。
	//複数のオブジェクトが適合する場合はtoStringで文字列化して、カンマつなぎにする。
	//varNameがコマンドから始まる場合、DBやObjectにアクセスする。
	public static Object getValue(String varName) {
		if (varName.startsWith("dbtest ")) { //$NON-NLS-1$
			return selectByJsonPath(varName);
		} else if (varName.matches("^\"?\\$[0-9_]+.*\"?$")) { //$NON-NLS-1$
			return getValue("objtest " + varName.replace("\"", ""));
		} else if (varName.matches("^objtest\\s+\"?\\$[0-9_]+.*$")) { //$NON-NLS-1$
			//インスタンス番号を省略の場合、現在のコマンドであるので情報を補う。
			//TestCommandRecordの内部クラスAssertMacroRecordで使用する。
			String t_key = varName;
			t_key = varName.replaceFirst("^objtest\\s+\"?(\\$[0-9_]+[^\"]*)\"?$", "objtest " + Integer.toString(Tool.curID) + "$1"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

			return getValue(t_key);
		} else if (varName.startsWith("objtest ")) { //$NON-NLS-1$
			//生成されたインスタンスの属性値をJsonPathにより取得する
			final Pattern t_pat = Pattern.compile("objtest\\s+\"?([\\w\\$]+)([^\"]*)\"?"); //$NON-NLS-1$

			Matcher t_m = t_pat.matcher(varName);
			if (t_m.find()) {
				String obj_id = t_m.group(1);
				String jpath = t_m.group(2);

				Object t_obj = null;
				if (obj_id.startsWith("$.")) { //$NON-NLS-1$
					return _value(_map, jpath);
				} else {
					if (obj_id.startsWith("$")) { //$NON-NLS-1$
						//TestCmdのIDがなければ、補完する。
						obj_id = Integer.toString(Tool.curID) + obj_id;
					}
					t_obj = Tool.getInstance(obj_id);
					if (t_obj != null) {
						if (jpath.length() > 0) {
							return _value(t_obj, jpath);
						} else {
							return t_obj;
						}
					}
				}
			}
		} else if (_map != null) {
			//大域変数参照
			Object t_obj = _value(_map, varName);
			if (t_obj != null && t_obj.getClass().getName().equals("java.lang.String")) { //$NON-NLS-1$
				//文字列なら別名に置き換える可能性ありなので、変換を試みる。
				return Tool.convert2Alias(t_obj.toString()); //$NON-NLS-1$
			}
			return t_obj;
		}
		return null;
	}

	//引数のオブジェクトの属性をpathを辿って返す。
	@SuppressWarnings("rawtypes")
	private static Object _value(Object obj, String path) {
		String jpath = path;
		jpath = jpath.replaceFirst("^\\.*(.+?)$", "$1");
		if (!jpath.startsWith("$")) {
			jpath = "$." + jpath;
		}

		boolean isSize = false;
		if (jpath.endsWith(".length()")) {
			//sizeの取得フラグ
			isSize = true;
			jpath = jpath.substring(0, jpath.length() - 9);
		}
		if (jpath.startsWith("$.[")) {
			//いきなり配列の場合は$.ではなく$[から始まる。
			jpath = "$" + jpath.substring(1); //$NON-NLS-1$
		}
		ArrayList<Object> t_list = Fson.valueByJsonPath(obj, jpath);
		if (t_list.size() > 0) {
			Object t_obj = t_list.get(0);
			if (isSize) {
				if (t_obj == null) {
					return Integer.valueOf(-1);
				}
				Class<?> t_c = t_obj.getClass();
				if (t_c.isArray()) {
					return Array.getLength(t_obj);
				} else if (List.class.isAssignableFrom(t_c)) {
					return ((List)t_obj).size();
				} else if (Map.class.isAssignableFrom(t_c)) {
					return ((Map)t_obj).size();
				}
				Tool.logForTesting(null, jpath + "\" has not length/size method.");
				return Integer.valueOf(-1);
			}
			return t_obj;
		}
		return null;

	}

	//ログの仲介
	public static void logging(String str) {
		TestLogger.out("@out>>  " + str); //$NON-NLS-1$
	}

	/////////////////////////////////////////////////////////////
	//以下はXMLアクセスのための便利関数
	private static Document s_doc;

	//XMLを読み込む
	public static void loadXML(String src) throws ParserConfigurationException, SAXException, IOException {
		try {
			DocumentBuilderFactory t_fact = DocumentBuilderFactory.newInstance();
			DocumentBuilder t_builder = t_fact.newDocumentBuilder();
			s_doc = t_builder.parse(new ByteArrayInputStream(src.getBytes("UTF-8"))); //$NON-NLS-1$
		} catch (ParserConfigurationException e) {
			AssertRecord.setLost(e.getMessage());
			throw e;
		} catch (SAXException e) {
			AssertRecord.setLost(e.getMessage());
			throw e;
		} catch (IOException e) {
			AssertRecord.setLost(e.getMessage());
			throw e;
		}
	}

	//引数で指定されたXPathの要素を返す。
	public static String xmlContent(String path) throws XPathExpressionException {
		XPath t_xpath =  XPathFactory.newInstance().newXPath();

		try {
			return t_xpath.evaluate(path, s_doc);
		} catch (XPathExpressionException e) {
			AssertRecord.setLost(e.getMessage());
			throw e;
		}
	}

	//($com.ftinc.si.assist.run.VCentral.getValueByXpath(obj, xpath)) の評価式。
	//文字列に評価式を埋め込み、Fsonで評価する。objはインスタンステーブルに登録した名前。Fakeならクラス名。
	public static String getValueByXpath(String obj, String xpath) throws ParserConfigurationException, SAXException, IOException, XPathExpressionException {
		String xhtml = html2XML(getFakeObject(obj).toString());//getFakeObjectはインスタンステーブルにあるならそれを返す。Fakeなら
		loadXML(xhtml);
		return xmlContent(xpath);
	}

	//終了タグのないtag自動補足変換用のmap
	private static ArrayList<String[]> s_tag_adjustmap = new ArrayList<String[]>();


	//HTMLをXMLに変換する。
	public static String html2XML(String data) {
		//キー：ターゲットtag（終了タグが省略可）, 値：配列の要素[終了判定用tag 途中の文字列に含まれてはいると単純に終了タグ追加できないtag]
		//途中に</tag>があれば何もしない。条件があえば、終了タグを挿入する。
		s_tag_adjustmap.add(new String[] {"li", "(<li[\\s>]|</ol>|</ul>)", "(<ol[\\s>]|<ul[\\s>]|</li>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"dt", "(<dt[\\s>]|</dl>)", "(<dl[\\s>]|</dt>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"dd","(<dd[\\s>]|</dl>)", "(<dl[\\s>]|</dd>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"optgroup", "(<optgroup[\\s>]|</select>)", "(<select[\\s>]|</optgroup>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"option", "(<optgroup[\\s>]|</select>)", "(<select[\\s>]|</option>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"tr", "<(tr[\\s>]|</table>)", "(<table[\\s>]|</tr>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"th","(<th[\\s>]|</tr>|</table>)", "(<table[\\s>]|</th>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"td", "(<td[\\s>]|</tr>|</table>)", "(<table[\\s>]|</td>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"tbody", "(<tbodyt[\\s>]|<tfoot[\\s>]|</table>)", "(<table[\\s>]|</tbody>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"thead", "(<tfoot[\\s>]|<tbody[\\s>]|<thead[\\s>]|</table>)", "(<table[\\s>]|</thead>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"tfoot","(<<tfoot[\\s>]|</table>)", "(<table[\\s>]|</tfoot>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		s_tag_adjustmap.add(new String[] {"a", "(<a[\\s>]|</ol>|</ul>|</div>|</table>)", "(<table[\\s>]|<td|<ul[\\s>]|<ol[\\s>]|<div[\\s>]|</a>)"}); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

		//doctypeを取る。
		String t_str = data.replaceFirst("(?i)<!doctype.*?>", ""); //$NON-NLS-1$ //$NON-NLS-2$

		//終了タグから空白を除去する。
		t_str = Tool.regReplaceAll(t_str, "<\\s*/([a-zA-Z\\-]+)\\s*>", "</$1>");

		//html属性は不要なので削除。ごてごてと属性があるpageがあり、エラーの元になる。
		t_str = t_str.replaceFirst("(?i)<html[^>]*?>", "<html>");

		//headは不要
		t_str = t_str.replaceFirst("(?i)<head[\\s\\S]+?</head[^>]*>", "");

		//meta,linkタグは不要なので削除。
		t_str = Tool.regReplaceAll(t_str, "(?i)<(meta|link)(\\s[^>]*?/?\\s*>|>)", "");

		//★★(?i)　は以後の正規表現で大文字小文字を区別しないフラグ　★★★
		//<p></p><rp></rp><rt></rt><colgroup></colgroup>は単純に除去する。省略条件は複雑なので。
		t_str = Tool.regReplaceAll(t_str, "(?i)</?[^apbiqu]\\s*>", "");   //正式な終了タグのない1文字タグを除去する。たぶん関係ない。 //$NON-NLS-1$ //$NON-NLS-2$
		t_str = Tool.regReplaceAll(t_str, "(?i)</?(br|p|rp|rt|nav|colgroup)(\\s[^>]*?>|>)", ""); //$NON-NLS-1$ //$NON-NLS-2$

		//html特殊文字の除去
		t_str = Tool.regReplaceAll(t_str, "&\\w+;", ""); //$NON-NLS-1$ //$NON-NLS-2$

		//scriptの除去。xml解析には不要
		t_str = Tool.regReplaceAll(t_str, "(?i)<script[\\s\\S]*?</script>", ""); //$NON-NLS-1$ //$NON-NLS-2$
		t_str = Tool.regReplaceAll(t_str, "(?i)<noscript[\\s\\S]*?</noscript>", ""); //$NON-NLS-1$ //$NON-NLS-2$

		//コメントの除去
		t_str = Tool.regReplaceAll(t_str, "\\s*<!\\-\\-[\\s\\S]*?\\-\\->\\s*", "");

		//異常タグの除去：タグ名が不正
		t_str = Tool.regReplaceAll(t_str, "<[^a-zA-Z/][^>]*>", "");

		//時々ある、明確に異常な属性の除去（javascriptによる属性変更の失敗？）
		//repairAttrsDQTの前に行うこと。""がベアでない可能性があるため。例「a=""  "=""」
		t_str = Tool.regReplaceAll(t_str, "\\s[^\\s]*[\\+\\*/\\-\\.:;\\?,@\\^\\|='#\\$\\]\\[\\(\\)\\{\\}&%#!>\"<]=\"[^\"]*\"", " ");

		//文字列"や'に囲まれている領域の退避。大きい文字列は30を上限とする。
		ArrayList<String> esq_list = CodeProcessor.escDQStrings2(t_str, "@@@@", 30);  //文字列を"@@@@番号"に置換する。
		t_str = esq_list.get(0);

//		Tool.alertMSG(null, "after esqDQString in html2XML>>" + t_str);

		//属性値が""で囲まれていない場合、囲む。
		t_str = Tool.regReplaceAll(t_str, "(<[a-zA-Z\\-]+\\s*[^>]*\\s[a-zA-Z\\-]+=)([^\\s\">]+)([\\s>])", "$1\"$2\"$3");

		//属性名のみあって値の設定がない場合、="?"を付ける。
		//"の直後にある場合。
		t_str = Tool.regReplaceAll(t_str, "(\"[\\s]+[a-zA-Z\\-]+)(\\s+[a-zA-Z]|\\s*[/>])",  "$1=" + Matcher.quoteReplacement("\"?\"") + " $2");

		//タグ名直後にある場合。
		t_str = Tool.regReplaceAll(t_str, "(<[a-zA-Z\\-]+\\s+[a-zA-Z\\-]+)(\\s+[a-zA-Z]|\\s*[/>])",  "$1=" + Matcher.quoteReplacement("\"?\"") + " $2");

		//開始タグや属性名を小文字にする。repairAttrsDQTの前に行う。属性名を小文字にしてからでないとrepairAttrsDQTがうまく動かない。
		t_str = tagsToLowerCase(t_str);

//		Tool.alertMSG(null, "after tagToLower in html2XML>>" +  + t_str);

		//タグの中の重複する属性を削除する。※どうしようもないアホなWebページでは、こういうことがある。
		t_str = removeDupAttrsInTag(t_str);

//		Tool.alertMSG(null, "after removeDup in html2XML>>" + t_str);

		//終了タグをつけられない<tag...>を<tag... />にする。
		String tag_str = "(wbr|hr|img|col|base|link|meta|input|keygen|area|param|embed|source|track|command|frame)"; //$NON-NLS-1$
		t_str = Tool.regReplaceAll(t_str, "(?i)(<" + tag_str + ")(\\s[^>]*[^/]>|>)", "$1/>"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

		//hiddenの除去
		t_str = Tool.regReplaceAll(t_str, "(?i)\\s*<input\\s*[^>]*?type=['\"]hidden[\"'][^>]*?>\\s*", "");

		//xml宣言の追加
		if (t_str.matches("^.*<\\?xml.*?>.*$")) { //$NON-NLS-1$
			t_str = "<?xml version=\"1.0\" encoding=\"Shift_JIS\" ?>" + t_str; //$NON-NLS-1$
		}
		//変換
		ArrayList<String> t_list = new ArrayList<String>();
		t_str = adjustCloseTags(t_str, t_list, 0);
//		Tool.alertMSG(null, "After adjust \n" + t_str);

		for (int i = t_list.size() - 1; i >= 0; i--) {
			t_str = Tool.replaceB(t_str, "<Z" + Integer.toString(i) + "/>", t_list.get(i));
		}

		//退避した文字列を元に戻す。
		//javascriptの予期しない副作用除。属性名だけ存在する場合がある。xmlとしては異常なので削除する。
		//※必ずコメント除去の後に行うこと。-->がマッチしてしまうため。また[^]の中に<を入れるのは重要。<LI>が>になってしまわないように。
		t_str = repairAttrsDQT(t_str, esq_list);
//		Tool.alertMSG(null, "After repair in html2XML>>" + t_str);

		return t_str;
	}

	//xmlの規約ではタグは小文字から始まる。どうせなら全部小文字にする。どうせHTMLだし。
	private static String tagsToLowerCase(String str) {
		String t_str = str;
		ArrayList<String> done_list = new ArrayList<String>();

		final Pattern t_pat = Pattern.compile("</?[\\w\\-]+[\\s>]|\\s[\\w\\-]+=");
		Matcher t_m = t_pat.matcher(t_str);
		while (t_m.find()) {
			String t_val = t_m.group();
			if (!done_list.contains(t_val)) {
				t_str = Tool.replaceB(t_str, t_val, t_val.toLowerCase());
				done_list.add(t_val);
			}
		}
		return t_str;
	}


	//javascriptのいたずら? 属性名だけ存在したり、重複したりする場合がある。xmlとしては異常なので削除する。'!!!!!!!!!!!!!!
	//※必ずコメント除去の後に行うこと。-->がマッチしてしまうため。また[^]の中に<を入れるのは重要。<LI>が>になってしまわないように。
	private static String repairAttrsDQT(String str, ArrayList<String> esq_list) {
		for (int j = 1; j < esq_list.size(); j++) {
			String t_val = esq_list.get(j);

			//属性値である文字列中の<>&をエスケープする。元は禁止文字であったことを示すため!を最初につける。
			//エスケープ文字はJavaに渡す前に元に戻す。
			t_val = Tool.replaceB(t_val, "<", "!&lt;");
			t_val = Tool.replaceB(t_val, ">", "!&gt;");
			t_val = Tool.replaceB(t_val, "&", "!&amp;");

			esq_list.set(j, t_val);
		}
		str = CodeProcessor.restoreDQString(str, "@@@@", esq_list);
		return str;
	}


	//タグ属性のなかの重複を除去する。あほなHTMLにはありうる。
	private static String removeDupAttrsInTag(String str) {
		String t_str = str;

		Pattern t_pat1 = Pattern.compile("<[^/][\\w\\-]*\\s+([\\s\\S]+?)>");
		Matcher t_m = t_pat1.matcher(t_str);
		while (t_m.find()) {
			ArrayList<String> t_list = new ArrayList<String>();

			String n_part = t_m.group();
			String n_replaced = n_part;

			Pattern t_pat2 = Pattern.compile("([\\w\\-]+)(\\s*=\\s*\")([^\"]+)\"");
			Matcher t_m1 = t_pat2.matcher(t_m.group(1));
			while (t_m1.find()) {
				if (t_list.contains(t_m1.group(1))) {
					n_replaced = Tool.replaceB(n_replaced, t_m1.group(), "");
				} else {
					t_list.add(t_m1.group(1));
				}
			}
			if (n_replaced.length() < n_part.length()) {
				t_str = Tool.replaceB(t_str, n_part,  n_replaced);
			}
		}
		return t_str;
	}


	//tag, 直後タグ vbtab 途中の文字列に含まれてはいると処理できない文字
	//途中に</tag>があれば何もしない。条件があえば、終了タグを挿入する。
	private static String adjustCloseTags(String str, ArrayList<String> xlist, int pos) {
		boolean is_top = false;
		if (str.startsWith("<?xml = version=")) {
			is_top = true;
		}

		String t_str = str;
		int n = pos;
		while (s_tag_adjustmap.size() > n) {
//			boolean go_next = true;

			String[] cur_elm = s_tag_adjustmap.get(n);//今補正しようとしているタグと抽出ルール
			String regHead = cur_elm[0];										//li
			String regTail = cur_elm[1];//終了タグが省略できる次のタグ		//(<li[\s>]|</ol>|</ul>)
			String regNG = cur_elm[2];										//(<ol[\s>]|<ul[\s>]|</li>)

			ArrayList<String> dups = new ArrayList<String>(); //重複箇所は一気に変換するので、処理済みの文字列パターンを登録する。

			//z-suppled属性がないパターンを抽出する。
			Pattern t_pat = Pattern.compile("(?i)<" + regHead + "(\\s*>|\\s[^z][^>]+>)([\\s\\S]*?)" + regTail); //$NON-NLS-1$ //$NON-NLS-2$
			Matcher t_m0 = t_pat.matcher(t_str);
			if (t_m0.find()) {
				//注意:飛び飛びの可能性もあるということ。

				String next_part = t_m0.group();//マッチした全体の文字列. //終わりが</ul>の場合、>まで含む。<liの場合、そこまで切り取らない。
				if (!t_m0.group(1).contains(" z_suppled=") && !dups.contains(next_part)) {
//					go_next = false;

					String next_replaced = "";
					String htag = "<" + regHead + t_m0.group(1); //<li>
					String inner_txt  = t_m0.group(2) + t_m0.group(3);//途中以降の文字列
					String tag_tail = t_m0.group(3).toLowerCase();//li/ul>
					String tag_tail2 = tag_tail.replaceFirst("^</?(.*)[\\s>]$", "<$1");//次のタグの前に位置する開始型タグ(<付き)  <li_

					//inner_txtからtag_tailを除いた文字列にNGタグがないか確認する。regHeadからregNGの間は最短である。
					Pattern t_patNG = Pattern.compile("(?i)^([\\s\\S]*?)" + regNG + "([\\s\\S]*)" + regTail + "$");
					Matcher t_m = t_patNG.matcher(inner_txt);
					if (t_m.find()) {
						//NGタグがある。加工は要注意。
						String tag_ng = t_m.group(2).toLowerCase(); //<ul[\\s>] or </li>
						String tag_ng2 = tag_ng.replaceFirst("</?([a-zA-Z]+)[>\\s]?", "$1"); //NGタグ名（<なし）。先頭に/があるものは(A)分岐に行くはず。
						if (regHead.equals(tag_ng2)) {
							if (next_part.matches("(?i)^<(" + regHead + "[\\s>][\\s\\S]*?<)(/" + regHead + "[\\s\\S]*)$")) {
								//NGタグで自分自身は、/付のみ。
								//<li>...</li><li>のパターン。NGタグがregHeadの閉じタグの場合、z-suppled属性をつける。
								next_replaced = next_part.replaceFirst("(?i)^(<" + regHead + ")(>|\\s[^>]*>)([\\s\\S]*</" + regHead + ")", "$1 z-suppled=\"true\"$2$3");

								t_str = Tool.replaceB(t_str, next_part, next_replaced);
							} else {
								n++; //無限ループ回避の逃げ
							}
						}else if (tag_ng.contains("</")) {
							//ここに来ないはず。別の分岐の方が先。
							n++; //無限ループ回避の逃げ
						} else {
							//<li>...<ul>..<li>のパターン。間に</ul>はない。その次の</ul>まで取得する。
							//tag_ngは</から始まらなければ構造を持つ。★★★★★
							//<li>...<ul>...</ul>...<次のregTail>
							//①<ul>...</ul>を切り出して再帰処理して完全XMLにする。

							//第二引数は<tag_ng2を探し始める位置
							String sub_xml = Tool.getClosedBlock(t_str, t_m.end(1) + 1, "<" + tag_ng2, "</" + tag_ng2 + ">");
							if (sub_xml != null && sub_xml.length() > 0) {
								//②それを<Z数字/>に置き換え、collectionに登録する。同じタグ処理から行う。
								next_replaced = sub_xml;
								int k = n;
								if (!next_replaced.matches("^[\\s\\S]*<" + regHead + "(?!\\sz\\-suppled=)[\\s\\S]*$")) {
									k++; //regHeadはnext_replacedの中では処理済み。
								}
								for (int i = k; i < s_tag_adjustmap.size(); i++) {
									String[] t_tags = s_tag_adjustmap.get(i);
									if (next_replaced.matches("^.*<" + t_tags[0] + "(>|\\s[^>]*>)")) {
										//'一つでも処理していなかったら残った補完処理をして置換する。
										next_replaced = adjustCloseTags(next_replaced, xlist, i);
										break;
									}
								}
								next_replaced = "<Z" + Integer.toString(xlist.size()) + "/>";
								xlist.add(next_replaced);

								t_str = t_str.replaceFirst("(?i)(" + Pattern.quote(htag + t_m.group(1)) + ")" + Pattern.quote(sub_xml), "$1" + next_replaced);

								//もう一度、同じタグに挑戦のため抜ける。この外側に未処理のタグがあるかも知れないため。
								break;
							} else {
								Tool.alertMSG(null, "adjustCloseTags>>end of tag_ng2=" + tag_ng2 + "@(pos=" + Integer.toString(t_m.end(1) + 1) + ") not found.\n\nhtml=" + t_str);
								n++; //無限ループ回避の逃げ
							}
						}
					} else {
						//NGのタグがない場合、置き換える。「...」は最小単位にすること。
						//tag_tailは</から始まるとき、構造を持つ。★★★★★

						if (tag_tail.startsWith("</")) {
							//<li>...</ul>　⇒　遡って<ul>を見つけ、<ul>...<li>...</ul>の範囲を処理すること。outer_txtの内側になければ、さらに前を探す。
							Pattern t_pat3 = Pattern.compile("(?i)" + tag_tail2 + "(>|\\s[^>]*>)" + "([\\s\\S]*?)" + tag_tail + "$");
							Matcher t_m3 = t_pat3.matcher(t_str);
							if (!t_m3.find()) {
								//<ul>が見つからなかったので、もっと先を探す。
								t_pat3 = Pattern.compile(tag_tail2 + "(>|\\s[^>]*>)" + "([\\s\\S]*?)" + Pattern.quote(next_part));
								t_m3 = t_pat3.matcher(t_str);
								if (t_m3.find()) {
									next_part = t_m3.group();

									//next_replaced に<li>は含まれる。
									Pattern t_pat4 = Pattern.compile("^(.*<" + regHead + ")(>|\\s[^>]*>)([\\s\\S]*)$");
									Matcher t_m4 = t_pat4.matcher(next_part);
									if (t_m4.find()) {
										String t_block = Tool.getClosedRange(t_m4.group(3));
										String t_reg = Pattern.quote(t_m4.group(1) + t_m4.group(2) + t_block);
										String t_repl = t_m4.group(1) + " z-suppled=\"true\"" + t_m4.group(2) + Matcher.quoteReplacement(t_block) + "</" + regHead + ">";
										next_replaced = next_part.replaceFirst(t_reg, t_repl);
									} else {
										next_replaced = "";
									}
								} else {
									next_replaced = "";
								}
							} else {
								next_part = t_m3.group();
								next_replaced = next_part;
							}
							if (next_replaced.length() > 0) {
								//置き換える文字列ができた。
								int k = n;
								if (!next_replaced.matches("^[\\s\\S]*<" + regHead + "(?!\\sz\\-suppled=)[\\s\\S]*$")) {
									k++; //regHeadはnext_replacedの中では処理済み。
								}
								for (int i = k; i < s_tag_adjustmap.size(); i++) {
									String[] t_tags = s_tag_adjustmap.get(i);
									if (next_replaced.matches("^.*<" + t_tags[0] + "(>|\\s[^>]*>)")) {
										//'一つでも処理していなかったら残った補完処理をして置換する。
										next_replaced = adjustCloseTags(next_replaced, xlist, i);
										break;
									}
								}
								xlist.add(next_replaced);
								next_replaced = "<Z" + Integer.toString(xlist.size()) + "/>";
								t_str = Tool.replaceB(t_str, next_part, next_replaced);
							} else {
								n++;
							}
						} else {
							//<li>...<li もしくは<td>...<th  この間隔は最短である。最初の<li>にz-suppled属性をつける。
							String t_block = Tool.getClosedRange(t_m0.group(2));

							//[\s\S]*には？を付けない。最後まで拾うため。
							next_replaced = next_part.replaceFirst("(?i)^(<" + regHead + ")(" + Pattern.quote(t_m0.group(1) + t_block) + ")([\\s\\S]*)", "$1 z-suppled=\"true\"$2</" + regHead + ">$3");
							t_str = Tool.replaceB(t_str, next_part,  next_replaced);
						}
					}
				}
			}
			//次に行ける条件（飛び飛びがなくなったら）で+1する。
//			if (go_next) n++;
			n++;
		}
		if (!is_top) {
			//再帰呼び出しならば、内側の処理はすべて完了しているので、外側に印をつける。
			t_str = suppled(t_str);
		}
		return t_str;
	}

	private static String suppled(String str) {
		String t_str = str;

		Pattern t_pat = Pattern.compile("^(<\\w+)([^>]*?>)");
		Matcher t_m = t_pat.matcher(t_str);
		if (t_m.find()) {
			t_str = t_m.replaceFirst("$1 z-suppled=\"true\"$2");
		}
		return t_str;
	}


	////////////////
	//Number, Booleanの取得関数, 比較関数

	//文字列から数字を切り出す。
	public static String pickNumber(String str) {
		if (str != null) {
			String t_str = ""; //$NON-NLS-1$
			t_str = str.replaceFirst("^[^0-9]*?([\\+0-9\\-\\.,]+)[\\s\\S]*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
			t_str = Tool.replaceB(t_str, ",", ""); //カンマを外す。 //$NON-NLS-1$ //$NON-NLS-2$
			if (t_str.matches("^[\\+\\-]?[0-9]+\\.?[0-9]*$")) { //$NON-NLS-1$
				return t_str;
			}
		}
		return null;
	}


	public static boolean greaterThan(String strA, String strB, boolean b_eq, boolean b_big) {
		//第一引数から数字のみを切り出す。
		strA = pickNumber(strA);

		if (strB.matches("^[\\+\\-]?[0-9]+$")) { //$NON-NLS-1$
			if (b_big) {
				BigInteger t_i = (BigInteger)Tool.getObjectfromJSON(BigInteger.class, strA);
				BigInteger t_j = (BigInteger)Tool.getObjectfromJSON(BigInteger.class, strB);
				if (b_eq) {
					return (t_i.compareTo(t_j) >= 0);
				} else {
					return (t_i.compareTo(t_j) > 0);
				}
			} else {
				Long  t_i = (Long)Tool.getObjectfromJSON(Long.class, strA);
				Long  t_j = (Long)Tool.getObjectfromJSON(Long.class, strB);
				if (b_eq) {
					return (t_i >= t_j);
				} else {
					return (t_i > t_j);
				}
			}
		} else if (strB.matches("[\\+\\-]?[0-9]+\\.?[0-9]+")) { //$NON-NLS-1$
			if (b_big) {
				BigDecimal t_i = (BigDecimal)Tool.getObjectfromJSON(BigDecimal.class, strA);
				BigDecimal t_j = (BigDecimal)Tool.getObjectfromJSON(BigDecimal.class, strB);
				if (b_eq) {
					return (t_i.compareTo(t_j) >= 0);
				} else {
					return (t_i.compareTo(t_j) > 0);
				}
			} else {
				Double  t_i = (Double)Tool.getObjectfromJSON(Double.class, strA);
				Double  t_j = (Double)Tool.getObjectfromJSON(Double.class, strB);
				if (b_eq) {
					return (t_i >= t_j);
				} else {
					return (t_i > t_j);
				}
			}
		}
		return false;
	}

	public static boolean lessThan(String strA, String strB, boolean b_eq, boolean b_big) {
		//第一引数から数字のみを切り出す。
		strA = pickNumber(strA);

		if (strB.matches("^[\\+\\-]?[0-9]+$")) { //$NON-NLS-1$
			if (b_big) {
				BigInteger t_i = (BigInteger)Tool.getObjectfromJSON(BigInteger.class, strA);
				BigInteger t_j = (BigInteger)Tool.getObjectfromJSON(BigInteger.class, strB);
				if (b_eq) {
					return (t_i.compareTo(t_j) <= 0);
				} else {
					return (t_i.compareTo(t_j) < 0);
				}
			} else {
				Long  t_i = (Long)Tool.getObjectfromJSON(Long.class, strA);
				Long  t_j = (Long)Tool.getObjectfromJSON(Long.class, strB);
				if (b_eq) {
					return (t_i <= t_j);
				} else {
					return (t_i < t_j);
				}
			}
		} else if (strB.matches("[\\+\\-]?[0-9]+\\.?[0-9]+")) { //$NON-NLS-1$
			if (b_big) {
				BigDecimal t_i = (BigDecimal)Tool.getObjectfromJSON(BigDecimal.class, strA);
				BigDecimal t_j = (BigDecimal)Tool.getObjectfromJSON(BigDecimal.class, strB);
				if (b_eq) {
					return (t_i.compareTo(t_j) <= 0);
				} else {
					return (t_i.compareTo(t_j) < 0);
				}
			} else {
				Double  t_i = (Double)Tool.getObjectfromJSON(Double.class, strA);
				Double  t_j = (Double)Tool.getObjectfromJSON(Double.class, strB);
				if (b_eq) {
					return (t_i < t_j);
				} else {
					return (t_i < t_j);
				}
			}
		}
		return false;
	}

	//正規表現の代わり：数字が等しい。もしくはJsonが等価である。
	public static boolean equalN(String strA, String strB, boolean b_big) {
		//第一引数から数字のみを切り出す。
		String xstrA = pickNumber(strA);

		//数字であるなら大小比較する。
		if (strB.matches("^[\\+\\-]?[0-9]+$")) { //$NON-NLS-1$
			if (b_big) {
				BigInteger t_i = (BigInteger)Tool.getObjectfromJSON(BigInteger.class, xstrA);
				BigInteger t_j = (BigInteger)Tool.getObjectfromJSON(BigInteger.class, strB);
				return (t_i.equals(t_j));
			} else {
				Long  t_i = (Long)Tool.getObjectfromJSON(Long.class, xstrA);
				Long  t_j = (Long)Tool.getObjectfromJSON(Long.class, strB);
				return (t_i.equals(t_j));
			}
		} else if (strB.matches("[\\+\\-]?[0-9]+\\.?[0-9]+")) { //$NON-NLS-1$
			if (b_big) {
				BigDecimal t_i = (BigDecimal)Tool.getObjectfromJSON(BigDecimal.class, xstrA);
				BigDecimal t_j = (BigDecimal)Tool.getObjectfromJSON(BigDecimal.class, strB);
				return (t_i.equals(t_j));
			} else {
				Double  t_i = (Double)Tool.getObjectfromJSON(Double.class, xstrA);
				Double  t_j = (Double)Tool.getObjectfromJSON(Double.class, strB);
				return (t_i.equals(t_j));
			}
		}
		//数字比較ではなく、文字列の完全一致。※enumは定義順の数字で比較する。
		return strA.equals(strB);
	}
}
