/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

//
//	ScormLoUtil
//	変更履歴
//		2004.02.01	新規作成
//

package jp.co.ntt.lms.lo.scorm;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import jp.co.ntt.lms.lo.LOException;
import jp.co.ntt.lms.lo.util.UserIDFolder;
import jp.co.ntt.lms.xmo.util.DebugLog;
import jp.co.ntt.lms.xmo.util.FileControl;

import org.apache.xpath.XPathAPI;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;

/**
 * ユーティリティ・メソッドクラス。
 * @author T.Nishiki
 */
public class ScormLoUtil {

	/**
	 * XML出力プロパティ名称：エンコード。
	 */
	public static final String PROPERTY_ENCODING = "encoding";

	/**
	 * XML出力プロパティ名称：XML宣言要否。
	 */
	public static final String PROPERTY_OMIT_XML_DECLARATION
		= "omit-xml-declaration";

	/**
	 * XML出力プロパティ：XML宣言不要。
	 */
	public static final String OMIT_XML_DECLARATION_YES = "yes";

	/**
	 * XML出力プロパティ：XML宣言必要。
	 */
	public static final String OMIT_XML_DECLARATION_NO = "no";

	/**
	 * 指定されたキーの値をコピーします。<br>
	 * ハッシュテーブルsrcより指定されたキーの値を取得し、
	 * nullでなければdestに同名称のキーで登録します。<br>
	 * @param dest コピー先ハッシュテーブル。
	 * @param src コピー元ハッシュテーブル。
	 * @param key コピーする値のキー名称。
	 * @throws NullPointerException dest、srcまたはkeyがnullの場合。
	 */
	public static void copyValue(Map dest, Map src, String key) {
		String value = (String)src.get(key);
		// 値がある場合のみ、コピー（登録）
		if (value != null) {
			dest.put(key, value);
		}
	}

	/**
	 * 指定されたキーの値をコピーします。<br>
	 * ハッシュテーブルsrcより指定されたキーの値を取得し、
	 * nullでなければdestに同名称のキーで登録します。<br>
	 * @param dest コピー先ハッシュテーブル。
	 * @param src コピー元ハッシュテーブル。
	 * @param keys コピーする値のキー名称の配列。
	 * @throws NullPointerException dest、srcまたはkeysがnullの場合。
	 */
	public static void copyValues(Map dest, Map src, String[] keys) {
		for (int i = 0; i < keys.length; i++) {
			copyValue(dest, src, keys[i]);
		}
	}

	/**
	 * 中断情報ファイルを取得します。<br>
	 * 引数から中断情報ファイルを表すFileオブジェクトを生成して戻します。
	 * このメソッドは、Fileオブジェクトを戻すだけであり、
	 * 中断情報ファイルが存在することを保証しません。<br>
	 * @param userID ユーザID。
	 * @param loID LOID。
	 * @param studySession 学習セッション回数。
	 * @return 中断情報ファイルを表すFileオブジェクト。
	 * @throws LOException 設定ファイルからの読み込みに失敗した場合。
	 */
	public static File getResumeFile(
		String userID,
		String loID,
		int studySession)
		throws LOException {
		// このメソッドの戻り値。
		File resumeFile = null;
		String resumeDirPath = null;
		ScormLoEnv scormEnv = new ScormLoEnv();

		resumeDirPath = scormEnv.getResumeDirPath();
		// 中断情報ファイルFileオブジェクトを生成します。
		// resumeDirPath/ユーザID/LOID/学習セッション回数/resume.xml
		resumeFile = getUserIDFolder(resumeDirPath, userID);
		resumeFile = new File(resumeFile, loID);
		resumeFile = new File(resumeFile, Integer.toString(studySession));
		resumeFile = new File(resumeFile, ScormLoConstant.RESUME_FILENAME);

		return resumeFile;
	}

	/**
	 * トレース情報ファイルを取得します。<br>
	 * 引数からトレース情報ファイルを表すFileオブジェクトを生成して戻します。
	 * このメソッドは、Fileオブジェクトを戻すだけであり、
	 * トレース情報ファイルが存在することを保証しません。<br>
	 * @param userID ユーザID。
	 * @param loID LOID。
	 * @param loginSession ログインセッション回数。
	 * @return トレース情報ファイルを表すFileオブジェクト。
	 * @throws LOException 設定ファイルからの読み込みに失敗した場合。
	 */
	public static File getTraceFile(
		String userID,
		String loID,
		int loginSession)
		throws LOException {
		// このメソッドの戻り値。
		File resumeFile = null;
		String resumeDirPath = null;
		ScormLoEnv scormEnv = new ScormLoEnv();

		resumeDirPath = scormEnv.getTraceDirPath();
		// 中断情報ファイルFileオブジェクトを生成します。
		// resumeDirPath/ユーザID/LOID/学習セッション回数/trace.xml
		resumeFile = getUserIDFolder(resumeDirPath, userID);
		resumeFile = new File(resumeFile, loID);
		resumeFile = new File(resumeFile, Integer.toString(loginSession));
		resumeFile = new File(resumeFile, ScormLoConstant.TRACE_FILENAME);

		return resumeFile;
	}

	/**
	 * 認証キーを生成します。<br>
	 * 指定されたユーザID、LOID、ホスト名称、ならびに現在日時をもとに
	 * 認証キーを生成します。
	 *
	 * @param userID ユーザID
	 * @param loID LOID
	 * @param hostName ホスト名称。
	 * @return 認証キー。
	 * @throws LOException 設定ファイルからの読み込みに失敗した場合。
	 */
	public static String makeAuthenticKey(
		String userID,
		String loID,
		String hostName)
		throws LOException {
		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD makeAuthenticKey("
				+ userID + "," + loID + ","  + hostName + ")",
			DebugLog.ROW);

		// このメソッドの戻り値
		String digestKey = null;

		ScormLoEnv scormEnv = new ScormLoEnv();
		int minuteSpan = scormEnv.getAuthenticMinuteSpan();

		// 現在日時を取得し、所定の時間幅に切り捨てる。
		Calendar calendar = Calendar.getInstance();
		truncateMinute(calendar, minuteSpan);
		// ダイジェストキーを算出する。
		digestKey =
			makeAuthenticKey(userID, loID, hostName, calendar.getTime());

		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL makeAuthenticKey("
				+ userID + "," + loID + "," + hostName + ")"
				+ " return " + digestKey,
			DebugLog.ROW);
		return digestKey;
	}

	/**
	 * 認証キーを生成します。<br>
	 * 指定されたユーザID、LOID、ホスト名称、日時をもとに
	 * 認証キーを生成します。
	 *
	 * @param userID ユーザID。
	 * @param loID LOID。
	 * @param hostName ホスト名称。
	 * @param dateTime 日時。
	 * @return 認証キー。
	 * @throws LOException 設定ファイルからの読み込みに失敗した場合。
	 */
	public static String makeAuthenticKey(
		String userID,
		String loID,
		String hostName,
		Date dateTime)
		throws LOException {
		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD makeAuthenticKey("
				+ userID + "," + loID + ","	+ hostName
				+ "," + dateTime + ")",
			DebugLog.ROW);

		// ダイジェストキーの元となる平文
		String message = null;
		// ダイジェスト算出用
		MessageDigest messageDigest = null;
		// ダイジェストキー（このメソッドの戻り値）
		String digestKey = null;
		// ダイジェストキーのバイト表現
		byte[] digestKeyBytes = null;
		// 日時変換用
		SimpleDateFormat dateFormat = null;
		try {
			// 引数から文字列を生成する
			dateFormat =
				new SimpleDateFormat(ScormLoConstant.AUTHENT_KEY_TIME_FORMAT);
			message = userID + loID + dateFormat.format(dateTime) + hostName;
			// ダイジェストキーを算出。
			messageDigest =
				MessageDigest.getInstance(
					ScormLoConstant.MESSAGE_DIGEST_ALGORITHM);
			digestKeyBytes = messageDigest.digest(message.getBytes());
			StringBuffer digestKeyBuffer = new StringBuffer();
			for (int i = 0; i < digestKeyBytes.length; i++) {
				digestKeyBuffer.append(
					Integer.toHexString((digestKeyBytes[i] & 0xf0) >> 4));
				digestKeyBuffer.append(
					Integer.toHexString(digestKeyBytes[i] & 0x0f));
			}
			digestKey = digestKeyBuffer.toString();

		}
		catch (NoSuchAlgorithmException e) {
			throw new LOException(e.getMessage());
		}
		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL makeAuthenticKey("
				+ userID + "," + loID + ","	+ hostName
				+ "," + dateTime + ")"
				+ " return " + digestKey,
			DebugLog.ROW);
		return digestKey;
	}

	/**
	 * 分数を切り捨てます。<br>
	 * 渡されたCalendarオブジェクトを指定された分単位で、切捨てします。
	 * @param calendar 日時オブジェクト。
	 * @param minuteSpan 切捨て分数。
	 */
	public static void truncateMinute(Calendar calendar, int minuteSpan) {
		// 分数
		int minute = calendar.get(Calendar.MINUTE);
		// 切り捨てる値
		int rest = minute % minuteSpan;
		calendar.add(Calendar.MINUTE, -rest);
	}

	/**
	 * SQL INSERT文を生成します。<br>
	 * 指定されたテーブル名称ならびにカラム情報から、INSERT文を生成します。
	 * 各カラムの種別ならびに使用するデータベースの種別に応じて、
	 * カラム値（文字列）を修正して、INSERT文を生成します。
	 * @param tableName テーブル名称。
	 * @param columnInfos カラム情報の配列。
	 * @return tableNameまたはcolumnInfosから求まるINSERT文。
	 * @throws LOException 環境ファイルからデータベースの種別を取得できなかった場合。
	 */

/*
	public static String makeInsertQuery(
		String tableName,
		ScormColumnInfo[] columnInfos) throws LOException {

		// メソッド開始ログ
		DebugLog.write(ScormLoUtil.class,
			"HEAD makeInsertQuery(" + tableName + "," + columnInfos + ")",
			DebugLog.ROW);
		// このメソッドの戻り値
		String query = null;
		try {
			////////////////////////////////////////////////////////////////////
			// 各カラム情報の精査、INSERT文の名称部分・値部分の生成
			// =================================================================
			// INSERT文のカラム名称部分
			StringBuffer columnNamePart = new StringBuffer();
			// INSERT文の値名称部分
			StringBuffer columnValuePart = new StringBuffer();
			// データベースの種別・エンコードを取得
			Environment env = new Environment();
			String dbType = env.getDBType();
			String dbEncode = env.getProperty("db_encode");
			for (int i = 0; i < columnInfos.length; i++) {
				// カラム情報を１件ずつ取得し、値を確認する。
				ScormColumnInfo columnInfo = columnInfos[i];
				String columnName  = columnInfo.getColumnName();
				String columnValue = columnInfo.getColumnValue();
				int columnType =  columnInfo.getColumnType();
				int columnSizeMax = columnInfo.getColumnSizeMax();
				int columnSizeUnit = columnInfo.getColumnSizeUnit();
				// nullの場合、INSERT文に含めない。
				if (columnValue == null) {
					continue;
				}
				// =============================================================
				// カラム名称の連結
				columnNamePart.append("," + columnName);
				// =============================================================
				// カラム値の連結(カラム種別、ＤＢ種別によって連結方法を変更)
				// 数値の場合、値（文字列）をそのまま
				if (columnType == ScormColumnInfo.COLUMN_TYPE_NUMBER) {
					columnValuePart.append("," + columnValue);
				}
				// 文字列の場合、文字列長チェックのうえ、超過部分はカットした上で、
				// 値（文字列）をシングルコーテションで囲む
				else if (columnType == ScormColumnInfo.COLUMN_TYPE_STRING) {
					String insertValue = null;
					if (columnSizeMax <= 0) {
						insertValue = columnValue;
					}
					else if (columnSizeUnit
							== ScormLoConstant.COLUMN_SIZE_UNIT_LENGTH) {
						if ( columnValue.length() > columnSizeMax) {
							insertValue =
								columnValue.substring(0, columnSizeMax);
						}
						else {
							insertValue = columnValue;
						}
					}
					else if (columnSizeUnit
							== ScormLoConstant.COLUMN_SIZE_UNIT_BYTE) {
						insertValue = truncateStringByByte(
							columnValue, columnSizeMax, dbEncode);
					}
					columnValuePart.append(",'" + insertValue + "'");
				}
				// 日時の場合、ＤＢの種別によって変更
				else if (columnType == ScormColumnInfo.COLUMN_TYPE_DATETIME) {
					// OracleはTO_DATE関数
					if ( dbType.equals(Environment.DB_ORACLE)) {
						columnValuePart.append(
							",TO_DATE('" + columnValue + "',"
							+ " '" + ScormLoConstant.DB_TODATE_FORMAT + "')");
					}
					// PostgreSQLはTO_TIMESTAMP関数
					else if ( dbType.equals(Environment.DB_POSTGRE)) {
						columnValuePart.append(
							",TO_TIMESTAMP('" + columnValue + "',"
							+ " '" + ScormLoConstant.DB_TODATE_FORMAT + "')");
					}
					// その他（SQL Server）は文字列として連結
					else {
						columnValuePart.append(",'" + columnValue + "'");
					}
				}
			}
			////////////////////////////////////////////////////////////////////
			// INSERT文の生成
			// =================================================================
			StringBuffer queryBuffer = new StringBuffer();
			queryBuffer.append("insert into ");
			// テーブル名称
			queryBuffer.append(tableName);
			queryBuffer.append("(");
			// カラム名称部分（先頭のカンマは外す）
			queryBuffer.append(columnNamePart.substring(1));
			queryBuffer.append(") values (");
			// カラム値部分（先頭のカンマは外す）
			queryBuffer.append(columnValuePart.substring(1));
			queryBuffer.append(")");
			// 生成したINSERT文を文字列として取得
			query = queryBuffer.toString();
		}
		catch (Exception e) {
			DebugLog.write(ScormLoUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}

		// メソッド終了ログ
		DebugLog.write(ScormLoUtil.class,
			"TAIL makeInsertQuery(" + tableName + "," + columnInfos + ")"
			+ " return " + query,
			DebugLog.ROW);
		return query;
	}
*/

	/**
	 * AICCフォーマットの日時文字列を解析します。<br>
	 * 引数で渡される日時文字列は、以下のフォーマットの従うものとします。<br>
	 * <code>YYYY[-MM[-DD[Thh[:mm[:ss[.s]]]]]]</code><br>
	 * 省略された個所は、月日は１、時以下は０が指定されたものとします。<br>
	 *
	 * @param dateString 日時文字列。<br>
	 * @return 指定された日時文字列を変換生成したDateオブジェクト。
	 *          nullの場合、年数を取得できなかった場合、nullを戻します。
	 */
	public static Date convertAiccFormatToDate(String dateString) {
		// このメソッドの戻り値。
		Date date = null;
		if (dateString != null) {
			////////////////////////////////////////////////////////////////////
			// 渡された文字列から各日時項目に分解
			StringTokenizer tokenizer =
				new StringTokenizer(dateString, "- T:.");
			////////////////////////////////////////////////////////////////////
			// 各日時項目を取得
			// =================================================================
			// 年
			int year = 0;
			if (tokenizer.hasMoreElements()) {
				String timeToken = tokenizer.nextToken();
				year = Integer.parseInt(timeToken);
				// 月（なければ１）
				int month = 0;
				if (tokenizer.hasMoreElements()) {
					timeToken = tokenizer.nextToken();
					month = Integer.parseInt(timeToken);
				}
				else {
					month = 1;
				}
				// 日（なければ１）
				int day = 0;
				if (tokenizer.hasMoreElements()) {
					timeToken = tokenizer.nextToken();
					day = Integer.parseInt(timeToken);
				}
				else {
					day = 1;
				}
				// 時
				int hour = 0;
				if (tokenizer.hasMoreElements()) {
					timeToken = tokenizer.nextToken();
					hour = Integer.parseInt(timeToken);
				}
				// 分
				int minute = 0;
				if (tokenizer.hasMoreElements()) {
					timeToken = tokenizer.nextToken();
					minute = Integer.parseInt(timeToken);
				}
				// 秒
				int second = 0;
				if (tokenizer.hasMoreElements()) {
					timeToken = tokenizer.nextToken();
					second = Integer.parseInt(timeToken);
				}
				// ミリ秒
				int milisecond = 0;
				if (tokenizer.hasMoreElements()) {
					timeToken = tokenizer.nextToken();
					milisecond = Integer.parseInt(timeToken);
				}
				////////////////////////////////////////////////////////////////
				// 取得したデータから時間オブジェクトを生成
				Calendar calendar = Calendar.getInstance();
				calendar.set(year, month - 1, day, hour, minute, second);
				calendar.set(Calendar.MILLISECOND, milisecond);
				date = calendar.getTime();
			}
		}
		return date;
	}

	/**
	 * Scormサーバが生成した日時文字列を変換します。
	 * @param dateString Scormサーバが生成した日時文字列。
	 * @return dateStringを変換した日時オブジェクト。
	 * @see ScormLoConstant#SCORM_SERVER_DATETIME_FORMAT
	 */
	public static Date convertScormServerFormatToDate(String dateString) {
		Date date = null;
		try {
			SimpleDateFormat dateFormat =
				new SimpleDateFormat(
					ScormLoConstant.SCORM_SERVER_DATETIME_FORMAT);
			date = dateFormat.parse(dateString);
		}
		catch (Exception e) {
		}
		return date;
	}
	/**
	 * 渡された日時データをデータベースで使用するために文字列に変換します。
	 * @param date 変換する日時。
	 * @return 変換後の日時文字列。
	 */
	public static String convertDateToDbFormat(Date date) {
		SimpleDateFormat dateFormat =
			new SimpleDateFormat(ScormLoConstant.MSSQL_DATETIME_FORMAT);
		String dateString = dateFormat.format(date);
		return dateString;
	}

	/**
	 * ファイルを移動します。<br>
	 *
	 * @param fileFrom 移動元ファイル。
	 * @param fileTo 移動先ファイル。
	 * @throws LOException ファイルアクセスに失敗した場合。
	 */
	public static void moveFile(File fileFrom, File fileTo)
		throws LOException {
		try {
			FileControl.copy(
				fileFrom.getAbsolutePath(),
				fileTo.getAbsolutePath());
			FileControl.delete(fileFrom.getAbsolutePath());
		}
		catch (IOException e) {
			DebugLog.write(ScormLoUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
	}

	/**
	 * Duration文字列を秒数に変換します。<BR>
	 * duration 文字列からフォーマットを調べる。<BR>
	 *
	 * @param duration Duration形式文字列<BR>
	 * @return durationの秒表現(ミリ秒は小数)<BR>
	 */
	public static double parseDuration(String duration) {

		// durationを年月日と時間に分ける
		String strDate = null;
		String strTime = null;
		int posBoundary = duration.indexOf("T");
		if (posBoundary != -1) {
			strDate = duration.substring(0, posBoundary);
			strTime = duration.substring(posBoundary);
		}
		else {
			strDate = duration;
			strTime = "";
		}

		// 年月日を取得
		double year = 0.0;
		double month = 0.0;
		double day = 0.0;
		if (strDate.length() > 0) {
			String buf = strDate.substring(1);
			if (buf.indexOf("Y") != -1) {
				String strYear = buf.substring(0, buf.indexOf("Y"));
				year = Long.parseLong(strYear);
				year = year * 365.0 * 24.0;
				year = year * 60.0 * 60.0;
				buf = buf.substring(buf.indexOf("Y") + 1);
			}
			if (buf.indexOf("M") != -1) {
				String strMonth = buf.substring(0, buf.indexOf("M"));
				month = Long.parseLong(strMonth);
				month = month * 31.0 * 24.0;
				month = month * 60.0 * 60.0;
				buf = buf.substring(buf.indexOf("M") + 1);
			}
			if (buf.indexOf("D") != -1) {
				String strDay = "";
				strDay = buf.substring(0, buf.indexOf("D"));
				day = Long.parseLong(strDay);
				day = day * 24.0;
				day = day * 60.0 * 60.0;
			}
		}
		// 時間、分、秒を取得
		double hour = 0.0;
		double min = 0.0;
		double sec = 0.0;
		if (!strTime.equals("")) {
			String buf = strTime.substring(1);
			if (buf.indexOf("H") != -1) {
				String strHour = buf.substring(0, buf.indexOf("H"));
				hour = Long.parseLong(strHour);
				hour = hour * 60.0 * 60.0;
				buf = buf.substring(buf.indexOf("H") + 1);
			}
			if (buf.indexOf("M") != -1) {
				String strMin = buf.substring(0, buf.indexOf("M"));
				min = Long.parseLong(strMin);
				min = min * 60.0;
				buf = buf.substring(buf.indexOf("M") + 1);
			}
			if (buf.indexOf("S") != -1) {
				String strSec = buf.substring(0, buf.indexOf("S"));
				sec = Double.parseDouble(strSec);
			}
		}

		double returnValue = year + month + day + hour + min + sec;

		return returnValue;
	}

	/**
	 * 指定したファイルをDOMDocumentに変換します。<br>
	 *
	 * @param file DOMDocumentに変換するXMLファイル。
	 * @return fileから生成されたDOMDocument。
	 * @throws LOException 指定したファイルを読み込めない、
	 *          または、XMLとして解釈できないなど例外が発生した場合。
	 */
	public static Document transformXmlFileToDocument(File file)
		throws LOException {
		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformXmlFileToDocument(" + file + ")",
			DebugLog.ROW);

		// このメソッドの戻り値。
		Document document = null;

		try {
			DocumentBuilderFactory factory =
				DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();

			// DOM Documentに変換。
			document = builder.parse(file);
		}
		// 例外はログに残してスローします。
		catch (Exception e) {
			DebugLog.write(ScormManifestUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		finally {
			// メソッド終了ログ
			DebugLog.write(
				ScormLoUtil.class,
				"TAIL transformXmlFileToDocument(" + file + ")"
					+ " return " + document,
				DebugLog.ROW);
		}
		return document;
	}

	/**
	 * 指定した入力ストリームをDOMDocumentに変換します。<br>
	 *
	 * @param stream DOMDocumentの生成元となる入力ストリーム。
	 * @return streamから生成されたDOMDocument。
	 * @throws LOException 指定したストリームから読み込めない、
	 *          または、XMLとして解釈できないなど例外が発生した場合。
	 */
	public static Document transformXmlStreamToDocument(InputStream stream)
		throws LOException {
		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformXmlFileToDocument(InputStream " + stream + ")",
			DebugLog.ROW);

		// このメソッドの戻り値。
		Document document = null;

		try {
			DocumentBuilderFactory factory =
				DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();

			// DOM Documentに変換。
			document = builder.parse(stream);
		}
		// 例外はログに残してスローします。
		catch (Exception e) {
			DebugLog.write(ScormManifestUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformXmlFileToDocument(InputStream " + stream + ")"
				+ " return "
				+ document,
			DebugLog.ROW);
		return document;
	}


	/**
	 * 指定したXML文字列をDOMDocumentに変換します。<br>
	 *
	 * @param xmlString DOMDocumentの生成元となるXML文字列。
	 * @return xmlStringから生成されたDOMDocument。
	 * @throws LOException 指定した文字列がXMLとして
	 *          解釈できないなど例外が発生した場合。
	 */
	public static Document transformXmlStringToDocument(String xmlString)
		throws LOException {
		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformXmlStringToDocument(" + xmlString + ")",
			DebugLog.ROW);

		Document document = null;
		try {
			DocumentBuilderFactory factory =
				DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();

			// DOM Documentに変換。
			StringReader reader = new StringReader(xmlString);
			InputSource source = new InputSource(reader);
			document = builder.parse(source);
		}
		// 例外はログに残してスローします。
		catch (Exception e) {
			DebugLog.write(ScormManifestUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformXmlStringToDocument(" + xmlString + ")"
				+ " return "
				+ document,
			DebugLog.ROW);
		return document;
	}

	/**
	 * 指定したXMLファイルを文字列として取得します。<br>
	 *
	 * @param file 取得元となるXMLファイル。
	 * @param outFormat 出力オプション・プロパティ。
	 *                   オプションがない場合はnullを渡します。
	 * @return 指定したXMLファイルに基づくXML文字列。
	 * @throws LOException 処理中に例外が発生した場合。
	 * @see Transformer#setOutputProperties(Properties)
	 *       出力オプション・プロパティ。
	 */
	public static String transformXmlFileToString(
			File file, Properties outFormat)
			throws LOException {

		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformXmlFileToString(" + file + "," + outFormat + ")",
			DebugLog.ROW);

		// ファイルからSourceを生成。
		Source source = new StreamSource(file);
		// 文字列へのResultを生成
		StringWriter writer = new StringWriter();
		Result result = new StreamResult(writer);
		// 変換
		transformXml(source, result, outFormat);
		// 変換結果を取得
		String xmlString = writer.toString();

		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformXmlFileToString(" + file + "," + outFormat + ")"
				+ " return " + xmlString,
			DebugLog.ROW);
		return xmlString;
	}

	/**
	 * 指定したXMLファイルを文字列ストリームに流します。<br>
	 *
	 * @param file 取得元となるXMLファイル。
	 * @param writer 書き込み先となる文字列ストリーム。
	 * @param outFormat 出力オプション・プロパティ。
	 *                   オプションがない場合はnullを渡します。
	 * @throws LOException 処理中に例外が発生した場合。
	 * @see Transformer#setOutputProperties(Properties)
	 *       出力オプション・プロパティ。
	 */
	public static void transformXmlFileToWriter(
			File file, Writer writer, Properties outFormat)
			throws LOException {

		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformXmlFileToWriter("
				+ file + "," + writer + "," + outFormat + ")",
			DebugLog.ROW);

		// ファイルからSourceを生成。
		Source source = new StreamSource(file);
		// 文字列ストリームへのResultを生成
		Result result = new StreamResult(writer);
		// 変換
		transformXml(source, result, outFormat);
		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformXmlFileToWriter("
				+ file + "," + writer + "," + outFormat + ")",
			DebugLog.ROW);
	}

	/**
	 * DOM Nodeをファイルに変換します。<br>
	 * DOM Nodeを指定されたファイルに出力します。
	 *
	 * @param node 元となるDOM Node。
	 * @param file 変換結果を出力するファイル。
	 * @param outFormat 出力オプション・プロパティ。
	 *                   オプションがない場合はnullを渡します。
	 * @throws LOException 処理中例外が発生した場合。
	 * @see Transformer#setOutputProperties(Properties)
	 *       出力オプション・プロパティ。
	 */
	public static void transformNodeToFile(
			Node node, File file, Properties outFormat) throws LOException {

		// メソッド開始ログ
		DebugLog.write( ScormLoUtil.class,
			"HEAD transformNodeToFile("
				+ node + "," + file + "," + outFormat + ")",
			DebugLog.ROW);
		// 出力先のディレクトリを作成する
		File dirFile = file.getParentFile();
		dirFile.mkdirs();
		// 元のDocumentからSourceを生成。
		Source source = new DOMSource(node);
		// 文字列へのResultを生成
		Result result = new StreamResult(file);
		// 変換
		transformXml(source, result, outFormat);

		// メソッド終了ログ
		DebugLog.write( ScormLoUtil.class,
			"TAIL transformNodeToFile("
				+ node + "," + file + "," + outFormat + ")",
			DebugLog.ROW);
	}

	/**
	 * DOM Nodeを変換してストリームに出力します。<br>
	 *
	 * @param node 元となるDOM Node。
	 * @param stream 変換結果を出力するストリーム。
	 * @param outFormat 出力オプション・プロパティ。
	 *                   オプションがない場合はnullを渡します。
	 * @throws LOException 処理中例外が発生した場合。
	 * @see Transformer#setOutputProperties(Properties)
	 *       出力オプション・プロパティ。
	 */
	public static void transformNodeToStream(
			Node node, OutputStream stream, Properties outFormat)
			throws LOException {

		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformNodeToStream("
				+ node + "," + stream + "," + outFormat + ")",
			DebugLog.ROW);

		// 元のDocumentからSourceを生成。
		Source source = new DOMSource(node);
		// 文字列へのResultを生成
		Result result = new StreamResult(stream);
		// 変換
		transformXml(source, result, outFormat);

		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformNodeToStream("
				+ node + "," + stream + "," + outFormat + ")",
			DebugLog.ROW);
	}

	/**
	 * DOM NodeからXML文字列に変換します。<br>
	 *
	 * @param node 元となるDOM Node。
	 * @param outFormat 出力オプション・プロパティ。
	 *                   オプションがない場合はnullを渡します。
	 * @return nodeを変換して求めたXML文字列を戻します。
	 * @throws LOException 変換処理中例外が発生した場合。
	 * @see Transformer#setOutputProperties(Properties)
	 *       出力オプション・プロパティ。
	 */
	public static String transformNodeToString(
		Node node, Properties outFormat)
		throws LOException {

		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformNodeToString(" + node + "," + outFormat + ")",
			DebugLog.ROW);

		// このメソッドの戻り値
		String xmlString = null;
		// 元のDocumentからSourceを生成。
		Source source = new DOMSource(node);
		// Resultを生成
		// 文字列に出力されるようにResultを生成
		StringWriter writer = new StringWriter();
		// Resultを生成
		Result result = new StreamResult(writer);
		// 変換
		transformXml(source, result, outFormat);
		// 変換結果から文字列を取得
		xmlString = writer.toString();

		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformNodeToString(" + node + "," + outFormat + ")"
				+ (" return " + xmlString),
			DebugLog.ROW);
		return xmlString;
	}

	/**
	 * ＸＭＬデータを変換します。<br>
	 *
	 * @param source 変換元ソース。
	 * @param result 変換結果を格納するオブジェクト
	 * @param outFormat 出力オプション・プロパティ。
	 *                   オプションがない場合はnullを渡します。
	 * @throws LOException 処理中に例外発生した場合。
	 * @see Transformer#setOutputProperties(Properties)
	 *       出力オプション・プロパティ。
	 */
	public static void transformXml(
			Source source, Result result, Properties outFormat)
			throws LOException {

		// メソッド開始ログ
		DebugLog.write(
			ScormLoUtil.class,
			"HEAD transformXml("
				+ source + "," + result + "," + outFormat + ")",
			DebugLog.ROW);
		try {
			// 変換オブジェクト生成
			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer transformer = factory.newTransformer();
			// オプションプロパティを設定
			if (outFormat != null) {
				transformer.setOutputProperties(outFormat);
			}
			// 変換
			transformer.transform(source, result);
		}
		catch (TransformerFactoryConfigurationError e) {
			DebugLog.write(ScormManifestUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}
		catch (Exception e) {
			DebugLog.write(ScormManifestUtil.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}

		// メソッド終了ログ
		DebugLog.write(
			ScormLoUtil.class,
			"TAIL transformXml("
				+ source + "," + result + "," + outFormat + ")",
			DebugLog.ROW);
	}


	/**
	 * 指定したノードの値を取得します。<br>
	 * XPathを利用して、指定した基点ノードから指定したパスでノードを検索し、
	 * 検索して見つかったノードの値を戻します。<br>
	 * @param originNode 基点となるノード。
	 * @param locationPath 検索するためロケーション・パス。
	 * @return 対象のノードが見つかった場合、そのノードの値を戻します。
	 *          見つからなかった場合、nullを戻します。
	 * @throws LOException 指定したパスがロケーション・パスが誤っているなど
	 * 　　　　　検索中に例外が発生した場合。
	 */
	public static String getNodeValue(Node originNode, String locationPath)
			throws LOException {
		////////////////////////////////////////////////////////////////////////
		// メソッド開始ログ
//		DebugLog.write(ScormAttemptDataAccess.class,
//			"HEAD getNodeValue(" + originNode + "," + locationPath + ")",
//			DebugLog.ROW);

		// このメソッドの戻り値
		String nodeValue = null;
		try {
			Node targetNode = XPathAPI.selectSingleNode(
									originNode, locationPath);
			if ( targetNode != null ) {
				nodeValue = targetNode.getNodeValue();
			}
		}
		catch (Exception e) {
//			DebugLog.write(ScormAttemptDataAccess.class, e, DebugLog.HIGHT);
			throw new LOException(e.getMessage());
		}

		////////////////////////////////////////////////////////////////////////
		// メソッド終了ログ
//		DebugLog.write(ScormAttemptDataAccess.class,
//			"TAIL getNodeValue(" + originNode + "," + locationPath + ")"
//			+ " return " + nodeValue,
//			DebugLog.ROW);
		return nodeValue;
	}

	/**
	 * 文字列をバイト長計算で切捨て処理します。
	 * @param str 切捨てを実行する文字列。
	 * @param maxLength 切り捨てるサイズ（バイト単位）。
	 * @param encode 文字列をバイト変換するためのエンコード。
	 * @return 切り詰めた文字列。
	 */
	public static String truncateStringByByte(
			String str, int maxLength, String encode) {
		// このメソッドの戻り値計算用
		StringBuffer valueBuffer = new StringBuffer(str.length());
		// バイト長計算用
		int byteCount = 0;
		for (int index = 0; index < str.length(); index++) {
			// 1文字ずつバイトに変換します。
			byte[] bytes = null;
			String s = str.substring(index, index + 1);
			try {
				bytes = s.getBytes(encode);
			}
			catch (Exception e) {
				break;
			}
			// 今回の１文字のバイト数を足し合わせると規定サイズを超える場合、終了。
			if (byteCount + bytes.length > maxLength) {
				break;
			}

			valueBuffer.append(s);
			byteCount += bytes.length;
		}
		return valueBuffer.toString();
	}

	private static File getUserIDFolder(String basePath, String userID) {
		return UserIDFolder.getFile(new File(basePath), userID);
	}
}

