#include "StdAfx.h"
#include "XMLTextParser.h"
#include "TypedBuffer.h"
#include "TextTools.h"

CXMLTextParser::CXMLTextParser(void)
{
}

CXMLTextParser::~CXMLTextParser(void)
{
}


/////////////////////////////////////////////////////////////////////////////////////
//	開始
/////////////////////////////////////////////////////////////////////////////////////
/*!
	スキャン
*/
void CXMLTextParser::StartScan(CString xml)
{
	xml.Trim();
	m_xmlData = xml.GetBuffer(0);
	m_dataLen = xml.GetLength();

	while(m_xmlData.GetLength() > 0)
		GetData(GetStartTag());
}


/*!
	ファイルから取得
*/
CString CXMLTextParser::LoadXmlFile(CString path)
{
	//	開く
	FILE	*in;
	if(_tfopen_s(&in, path, _T("rb")) != 0)
		throw CXMLTextParserException(_T("ファイルのオープンに失敗しました"), 0);
	
	//	サイズ取得
	fseek(in, 0, SEEK_END);
	long	len = ftell(in);
	fseek(in, 0, SEEK_SET);

	//	バッファ用意
	CTypedBuffer<char> buf(len + 1);

	//	読み込み
	fread(buf, len, 1, in);
	buf[len] = 0;
	fclose(in);

	//	変換
	CStringW	ret;
	int wbLen = MultiByteToWideChar(CP_UTF8, 0, buf, len, NULL, 0);
	MultiByteToWideChar(CP_UTF8, 0, buf, len, ret.GetBuffer(wbLen), wbLen);
	ret.ReleaseBufferSetLength(wbLen);

	return(CString(ret));
}


/////////////////////////////////////////////////////////////////////////////////////
//	タグ
/////////////////////////////////////////////////////////////////////////////////////
/*!
	開始タグ切り出し
*/
int CXMLTextParser::GetStartTag()
{
	//	タグチェック
	if(m_xmlData[0] != _T('<'))
		throw CXMLTextParserException(_T("異常な文字を検出しました"), m_dataLen - m_xmlData.GetLength());

	//	CDATA?
	if(m_xmlData.Left(9).CompareNoCase(_T("<![CDATA[")) == 0)
		throw CXMLTextParserException(_T("CDATAセクションは非対応です"), m_dataLen - m_xmlData.GetLength());

	//	タグを切り出す
	CString	tag;
	if(GetNextToken(m_xmlData, _T(">"), tag, 1))
		throw CXMLTextParserException(_T("異常なタグを検出しました"), m_dataLen - m_xmlData.GetLength());
	tag.Trim();

	//	特殊タグ
	if(tag[0] == _T('?') || tag[0] == _T('!'))
	{
		OnSpecialTag(tag);
		return(0);
	}

	//	異常なタグ
	if(!_istalpha(tag[0]) && tag[0] != _T('/'))
		throw CXMLTextParserException(_T("異常なタグを検出しました"), m_dataLen - m_xmlData.GetLength());

	//	タイプ検出
	if(tag[0] == _T('/'))
	{
		tag.Delete(0, 1);
		OnEndTag(tag);

		return(0);
	}
	else if(tag.Right(1) == _T("/"))
	{
		tag.Delete(tag.GetLength() - 1, 1);

		//	解析
		CTagOptionArray	keys;
		ParseTagOption(tag, keys);

		//	イベント
		OnStartTag(tag, keys);
		OnEndTag(tag);

		return(0);
	}
	else
	{
		//	解析
		CTagOptionArray	keys;
		ParseTagOption(tag, keys);

		//	イベント
		OnStartTag(tag, keys);

		return(1);
	}
}


/*!
	データ切り出し
*/
void CXMLTextParser::GetData(int enableEvent)
{
	//	終了/無し
	if(m_xmlData.IsEmpty() || m_xmlData[0] == _T('<'))
		return;

	//	データ検索
	CString	data;
	if(GetNextToken(m_xmlData, _T("<"), data, 0, FALSE))
		throw CXMLTextParserException(_T("異常なデータを検出しました"), m_dataLen - m_xmlData.GetLength());

	//	イベント
	if(enableEvent)
	{
		//	変換
		CTextEscape	escape;
		escape.AddEscape(_T("<"), _T("&lt;"));
		escape.AddEscape(_T(">"), _T("&gt;"));
		escape.AddEscape(_T("&"), _T("&amp;"));
		data = escape.UnEscape(data);

		//	イベント
		OnData(data);
	}
}


/*!
	タグオプションの解析
*/
void CXMLTextParser::ParseTagOption(CString &tag, CTagOptionArray &keys)
{
	//	タグ切り出し
	CString	tagName;
	tag.Replace(_T("\t"), _T(" "));
	if(!GetNextToken(tag, _T(" "), tagName))
	{
		while(1)
		{
			//	キー
			CString	key, data;
			if(GetNextToken(tag, _T("="), key))
				break;
			key.Trim();

			//	データ
			if(GetNextToken(tag, _T("\""), data))
				throw CXMLTextParserException(_T("異常なタグを検出しました"), m_dataLen - m_xmlData.GetLength());
			if(GetNextToken(tag, _T("\""), data))
				throw CXMLTextParserException(_T("異常なタグを検出しました"), m_dataLen - m_xmlData.GetLength());

			//	追加
			keys.Add(CTagOption(key, data));
		}
	}

	tag = tagName;
}


/////////////////////////////////////////////////////////////////////////////////////
//	ツール
/////////////////////////////////////////////////////////////////////////////////////
/*!
	指定した文字までを切り出す
	指定した文字は切り出し文字に含まれない

	指定した文字が見つからない場合は、-1が戻る
*/
int CXMLTextParser::GetNextToken(CString &src, CString find, CString &token, int tokenStart, int removeToken)
{
	//	検索する
	int tokenEnd = src.Find(find, tokenStart);
	if(tokenEnd == -1)
		return(-1);

	//	切り出し
	token = src.Mid(tokenStart, tokenEnd - tokenStart);

	//	削除
	if(removeToken)
		src.Delete(0, tokenEnd + find.GetLength());
	else
		src.Delete(0, tokenEnd);

	return(0);
}
