/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
////////////////////////////////////////////////////////////////////////////////
#Title: xbXml
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"

#include "xbXml.h"
#include "xbDnItf.h"

#pragma warning(push)
#pragma warning(disable: 4311) //troncation de pointeur de 'wchar *' à 'xbNptr'

#define xmlProtectBegin() \
	try \
	{

#define xmlProtectEnd() \
	} \
	catch(xbEdnRetry *pE) \
	{ \
		error(xbGetMsg("raw",pE),-1,-1,xbGetCtx("raw",pE)); \
		throw; \
	} \
	catch(xbEstack *) \
	{ \
		throw; \
	} \
	catch(xbEbase *pE) \
	{ \
		HRESULT hr = pE->GetHR(); \
		if (xbIsHRmemory(hr)) throw; \
		if (xbIsHRabort(hr)) throw; \
		error(xbGetMsg("raw",pE),-1,-1,xbGetCtx("raw",pE)); \
		pE->Delete(); \
	}

#define xmlProtect(code) \
	try \
	{ \
		code; \
	} \
	catch(xbEdnRetry *pE) \
	{ \
		error(xbGetMsg("raw",pE),-1,-1,xbGetCtx("raw",pE)); \
		throw; \
	} \
	catch(xbEstack *) \
	{ \
		throw; \
	} \
	catch(xbEbase *pE) \
	{ \
		HRESULT hr = pE->GetHR(); \
		if (xbIsHRmemory(hr)) throw; \
		if (xbIsHRabort(hr)) throw; \
		error(xbGetMsg("raw",pE),-1,-1,xbGetCtx("raw",pE)); \
		pE->Delete(); \
	}

void xbXmlParser::ParseStart(IxbXmlTarget *pTarget,LPCSTR pFileName,void *pData,ulong offsInFile,int col,int line)
{
	m_Flags.Clear();
	m_arrTarget.Clear();
	m_prTarget = pTarget;
	m_FileName = pFileName;

	m_col = col;
	m_line = line;
	m_offsInFile = offsInFile;

	m_txt.Empty();
	m_bTxtLineStart = true;
	m_sect.Empty();
	m_bInSection = false;
	m_bInSectionComment = false;
	m_bEndSect = false;
	m_bSectTerminated = false;
	m_pSectTextStart=m_pSectTextEnd=NULL;
	m_sectText.Empty();

	m_bInFlag = false;
	m_flag.Clear();

	m_cSpace = -1;
	m_chInStr = 0;
	m_bEscInStr = false;

	m_bIgnoreText = false;
	m_bTrimText = true;
	m_bIgnoreSect = false;

	m_bCaseDep = false;

	m_bInit = true;

	m_pData = pData;

	m_tabSize = 4;
}

int xbXmlParser::tabAlign(int col) const
{
	return ((col+m_tabSize-1) / m_tabSize) * m_tabSize;
}

void xbXmlParser::Parse(const char *pStr,DWORD cByteCnt)
{
//	xbRdbgAssert(memchr(pStr,'§',cByteCnt) == NULL);
	xbRdbgAssert(m_pSectTextStart==NULL&&m_pSectTextEnd==NULL);
	m_pSectTextStart = m_bInSection?pStr:NULL;
	m_pSectTextEnd = NULL;

	checkInit();
	if (!m_prTarget) 
		xbThrow xbExmlParser("xbXmlParser::Parse("+m_FileName+") : Target not set");

	while(cByteCnt > 0)
	{
		uchar ch = uchar(*(pStr++));
		cByteCnt--;

		if (ch == '\n') 
		{
			m_line++;
			m_col = 0;
			m_bTxtLineStart=true;
		}
		else 
		{
			if (ch == '\t')
				m_col = tabAlign(m_col+1);
			else
				m_col++;
		}
		m_offsInFile++;

		if (m_chInStr)
		{
			xbAssert(m_bInFlag);
			if (m_bEscInStr)
			{
				m_bEscInStr = false;
				if (m_flag.m_bValue)
				{
					m_flag.m_value += '\\';
					m_flag.m_value += ch;
				}
				else
				{
					m_flag.m_name += '\\';
					m_flag.m_name += ch;
				}
				continue;
			}
			if (ch == '\\')
			{
				m_bEscInStr = true;
				continue;
			}
			if (ch == m_chInStr)
			{
				m_chInStr = 0;
				m_cSpace = 0;
				if (m_flag.m_bValue)
					endPos(&m_flag.m_posValue);
				else
					endPos(&m_flag.m_posName);
				continue;
			}
			if (ch == '\n' || (ch == '>' && xmlTolerance()))
			{
				error("XML section '"+m_sect+"' / string not closed",
					m_flag.m_bValue?m_flag.m_posValue:m_flag.m_posName);

				m_chInStr = 0;
				m_cSpace = 0;
				if (m_flag.m_bValue)
					endPos(&m_flag.m_posValue);
				else
					endPos(&m_flag.m_posName);
				if (m_flag.m_bValue)
				{
					m_flag.m_value = xbString(m_flag.m_valueChStr) + m_flag.m_value;
					m_flag.m_valueChStr = 0;
				}
				else
				{
					m_flag.m_name = xbString(m_flag.m_nameChStr) + m_flag.m_name;
					m_flag.m_nameChStr = 0;
				}
				if (ch == '>')
				{
					m_col--;
					m_offsInFile--;
					cByteCnt++;
					pStr--;
				}
				continue;
			}
			if (m_flag.m_bValue)
				m_flag.m_value += ch;
			else
				m_flag.m_name += ch;
			continue;
		}
		if (((m_bInSection && !m_bIgnoreSect && !m_bInSectionComment) || m_bTrimText) 
			&& ::xbIsSpace(ch))
		{
			if (ch != '\n' && m_cSpace >= 0)
				m_cSpace++;
			else m_cSpace = 1;
			continue;
		}

		//1/ Cases outside XML sections
		if (!m_bInSection)
		{
			if (ch != '<')
			{
				if (m_cSpace>=0)
				{
					if ((!m_txt.IsEmpty()) && m_cSpace>0) 
						m_txt += xbStringPad(m_bTxtLineStart?1:m_cSpace);
					m_cSpace = -1;
				}
				m_bTxtLineStart = false;
				if (m_txt.IsEmpty())
					startPos(&m_txtPos);
				endPos(&m_txtPos);
				m_txt += ch;
				continue;
			}

			startPos(&m_sectPos);
			m_bInSection = true;
			m_sectText.Empty();
			m_pSectTextStart = pStr;
			continue;
		}
		m_bTxtLineStart = false;
		
		//2/ Cases inside XML sections
		if (ch == '<')
		{
			flagFeed(false);
			if (!m_bIgnoreSect) 
				error("XML section '"+m_sect+"' not closed",m_sectPos);
			if (m_txt.IsEmpty()) 
				m_txtPos = m_sectPos;
			m_sectPos.init();
			m_pSectTextEnd = pStr-1;
			m_txt += '<' + SectionAsText();
			endPos(&m_txtPos);
			sectionClear();
			cByteCnt++;
			pStr--;
			continue;
		}

		if (m_bInSectionComment)
		{
			//in <!-- comment... -->

			if (ch == '>'
				&& m_sect.Length >= 5
				&& m_sect.EndWith("--"))
			{
				endPos(&m_sectPos);
				m_pSectTextEnd = pStr-1;
				sectionFeed();

				continue;
			}

			m_sect += ch;
			continue;
		}

		if (ch == '>')
		{
			endPos(&m_sectPos);
			m_pSectTextEnd = pStr-1;
			sectionFeed();

			continue;
		}

		if (!m_bIgnoreSect)
		{
			if (ch == '=')
			{
				if (m_bInFlag && m_flag.m_bValue)
				{
					xbXmlFlag nextflag;
					if (m_flag.m_valueChStr != 0)
						startPos(&nextflag.m_posName);
					else
					{
						nextflag.m_name = m_flag.m_value;
						nextflag.m_posName = m_flag.m_posValue;
						m_flag.m_bValue = false;
						m_flag.m_posValue.init();
						m_flag.m_value.Empty();
					}
					
					flagFeed(false);
					m_flag = nextflag;
					xbAssert(m_bInFlag);
				}
				if (m_flag.m_posName.IsEmpty()) 
					startPos(&m_flag.m_posName);
				endPos(&m_flag.m_posName);
				m_bInFlag = true;
				m_flag.m_bValue = true;
				m_cSpace = -1;

				continue;
			}

			if (ch == '"' || ch == '\'')
			{
				if (m_bInFlag
					&& (m_flag.m_bValue && m_flag.m_valueChStr != ch
						&& (m_flag.m_value.IsValid() || m_flag.m_valueChStr != 0))
					||(!m_flag.m_bValue && m_flag.m_nameChStr != ch
						&& (m_flag.m_name.IsValid() || m_flag.m_nameChStr != 0))
					)
				{
					 flagFeed(false);
					 xbAssert(m_bInFlag);
				}

				if (m_flag.m_bValue && m_flag.m_posValue.IsEmpty()) 
					startPos(&m_flag.m_posValue);
				else if (m_flag.m_posName.IsEmpty()) 
					startPos(&m_flag.m_posName);

				m_bInFlag = true;
				m_chInStr = ch;

				if (m_flag.m_bValue) 
					m_flag.m_valueChStr = ch;
				else 
					m_flag.m_nameChStr = ch;

				continue;
			}
		}

		if (!m_bInFlag)
		{

			if (!m_bEndSect)
			{
				if (ch == '/' && m_sect.IsEmpty())
				{
					m_bEndSect = true;

					continue;
				}
				if (ch == '-' && m_cSpace < 0
					&& m_sect.Length == 2
					&& m_sect == "!-")
				{
					m_bInSectionComment = true;

					continue;
				}
			}

			if (m_cSpace<0 || m_sect.IsEmpty())
			{
				if (m_cSpace>=0) 
					m_cSpace = -1;
				if (m_bIgnoreSect && !m_bEndSect
					&& (m_sect.Length>1 || (m_sect.Length==1 && m_sect != "!")))
				{
					if (m_txt.IsEmpty()) 
						m_txtPos = m_sectPos;
					m_sectPos.init();
					m_pSectTextEnd = pStr;
					m_txt += '<' + SectionAsText();
//					m_txt += '<';
//					m_txt += ch;
					endPos(&m_txtPos);
					sectionClear();
					continue;
				}
				m_sect += ch;
				continue;
			}

			m_bInFlag = true;
			if (m_cSpace>=0) 
				m_cSpace = -1;
		}
		xbRdbgAssert(!m_bIgnoreSect && !m_bInSectionComment); 

		if (m_cSpace >= 0)
		{
			m_cSpace = -1;
			if (!m_flag.m_bValue || m_flag.m_value.IsValid() 
				|| m_flag.m_valueChStr) flagFeed(false);
		}

		if (m_flag.m_bValue)
		{
			m_flag.m_value += ch;
			if (m_flag.m_posValue.IsEmpty()) 
				startPos(&m_flag.m_posValue);
			endPos(&m_flag.m_posValue);
		}
		else
		{
			m_flag.m_name += ch;
			if (m_flag.m_posName.IsEmpty()) 
				startPos(&m_flag.m_posName);
			endPos(&m_flag.m_posName);
		}
	}
	if (m_pSectTextStart)
	{
		xbRdbgAssert(m_bInSection);
		m_sectText += xbString(m_pSectTextStart, (int)(xbNptr(pStr) - xbNptr(m_pSectTextStart)));
		m_pSectTextStart=NULL;
	}
}

void xbXmlParser::textFeed()
{
	if (m_txt.IsEmpty()) {m_txtPos.init();return;}
	if (!m_bIgnoreText) xmlProtect(m_prTarget->AddText(this,m_txt,m_txtPos));
	m_txt.Empty();
	m_txtPos.init();
}

void xbXmlParser::sectionFeed()
{
	flagFeed(true);
	textFeed();

	if (!m_bInSectionComment
		&& !m_bEndSect && !m_bSectTerminated 
		&& m_Flags.IsEmpty() && !m_sect.IsEmpty())
	{
		int ch = m_sect.Last();
		if (ch == '/') 
		{
			m_sect.Length = m_sect.Length - 1;
			m_bSectTerminated = true;
		}
	}
	
	xmlProtectBegin();
	int pos = m_bInSectionComment?-1:m_prTarget->CutSectionName(this,m_sect,m_bEndSect,m_bSectTerminated);
	if (pos >= 0 && pos < m_sect.GetLength())
	{
		xbString flgName = m_sect.Mid(pos);
		m_sect = m_sect.Left(pos);

		m_flag.m_posName = m_sectPos;
		m_flag.m_posName.m_col += pos;
		m_flag.m_posName.m_offsInFile += pos;
		m_flag.m_posName.m_lgInFile = flgName.Length;
		if (m_Flags.IsEmpty())
		{
			m_flag.m_name = flgName;
			m_Flags.Add(m_flag);
			m_flag.Clear();
		}
		else if (m_Flags.First().m_name.IsEmpty() && m_Flags.First().m_nameChStr == 0)
		{
			xbAssert(m_Flags.First().m_posName.m_lgInFile == 1);
			m_Flags.First().m_name = flgName;
			m_Flags.First().NameChange();
			m_flag.m_posName.m_lgInFile = 
				m_Flags.First().m_posName.m_offsInFile 
				- m_flag.m_posName.m_offsInFile 
				+ m_Flags.First().m_posName.m_lgInFile;
			m_Flags.First().m_posName = m_flag.m_posName;
		}
		else
		{
			m_flag.m_name = flgName;
			m_Flags.Insert(0,m_flag);
			m_flag.Clear();
		}
	}
	for(;;)
	{
		if (m_bEndSect) 
		{
			m_prTarget->AddSectionEnd(this,m_sect,m_sectPos);
			break;
		}
		if (!m_bIgnoreSect)
		{
			m_prTarget->AddSection(this,m_sect,m_bInSectionComment || m_bSectTerminated,m_sectPos);
			break;
		}
		if (!m_bIgnoreText)
		{
			m_prTarget->AddText(this,"<"+SectionAsText()+">",m_sectPos);
			break;
		}
		break;
	}
	xmlProtectEnd();

	sectionClear();
}

void xbXmlParser::flagFeed(bool bEnd)
{
	if (!m_bInFlag) return;

	if (bEnd && (m_flag.m_bValue?m_flag.m_valueChStr:m_flag.m_nameChStr) == 0)
	{
		xbString &str = m_flag.m_bValue?m_flag.m_value:m_flag.m_name;
		if (!str.IsEmpty() && m_cSpace <= 0)
		{
			int ch = str.Last();
			if (ch == '/') 
			{
				str.Length = str.Length - 1;
				m_bSectTerminated = true;
				if (m_flag.m_bValue) m_flag.m_posValue.m_lgInFile--;
				else m_flag.m_posName.m_lgInFile--;
			}
		}
		if (!m_flag.m_bValue && m_flag.m_name.IsEmpty()) 
		{
			m_flag.Clear();
			return;
		}
	}

	m_Flags.Add(m_flag);
	m_flag.Clear();
}

void xbXmlParser::sectionClear()
{
	m_sect.Empty();
	m_pSectTextStart=m_pSectTextEnd=NULL;
	m_sectText.Empty();
	m_bInFlag = false;
	m_bInSection = false;
	m_bInSectionComment = false;
	m_cSpace = -1;
	m_bEndSect = false;
	m_bSectTerminated = false;
	m_Flags.Clear();
	m_flag.Clear();
}

xbString xbXmlParser::SectionAsText() const
{
	checkInit();
	if (m_pSectTextEnd)
	{
		if (m_pSectTextStart)
			return m_sectText+xbString(m_pSectTextStart,(int)(xbNptr(m_pSectTextEnd)-xbNptr(m_pSectTextStart)));
		return m_sectText;
	}
	xbString str;
	if (m_bEndSect) str += '/';
	str += m_sect;

	for(int i = 0; i < m_Flags.GetLength(); i++)
	{
		str += ' ';
		const xbXmlFlag &flg = m_Flags.GetAt(i);
		if (flg.m_nameChStr != 0) str += flg.m_nameChStr;
		str += flg.m_name;
		if (flg.m_nameChStr != 0) str += flg.m_nameChStr;

		if (!flg.m_bValue) continue;

		str += '=';
		if (flg.m_valueChStr != 0) str += flg.m_valueChStr;
		str += flg.m_value;
		if (flg.m_valueChStr != 0) str += flg.m_valueChStr;
	}

	if (m_bSectTerminated) str += '/';
	
	return str;
}

const xbXmlFlag	*xbXmlParser::GetFlagPtr(int no) const
{
	checkInit();
	if (no >= m_Flags.GetLength()) return NULL;
	return &m_Flags.GetAt(no);
}

const xbXmlFlag	*xbXmlParser::GetFlagPtr(const xbString &name) const
{
	return getFlag(name);
}

bool	xbXmlParser::GetFlag(int no,xbXmlFlag *pFlg) const
{
	checkInit();
	if (no >= m_Flags.GetLength()) return false;

	if (pFlg) *pFlg = m_Flags.GetAt(no);

	return true;
}

bool	xbXmlParser::EnumFlag(int &idx,xbXmlFlag *pFlg) const
{
	checkInit();
	if (idx >= m_Flags.GetLength()) 
		return false;

	if (pFlg) 
		*pFlg = m_Flags.GetAt(idx);

	idx++;

	return true;
}

bool	xbXmlParser::EnumFlag(int &idx,xbXmlFlag **ppFlg) const
{
	checkInit();
	if (idx >= m_Flags.GetLength()) 
		return false;

	if (ppFlg) 
		*ppFlg = (xbXmlFlag*)&m_Flags.GetAt(idx);

	idx++;

	return true;
}

bool	xbXmlParser::GetFlag(const xbString &name,xbXmlFlag *pFlg) const
{
	const xbXmlFlag *flg = getFlag(name);
	if (!flg) return false;
	
	if (pFlg) *pFlg = *flg;
	
	return true;
}

bool	xbXmlParser::GetFlagValue(const xbString &name,xbString *pValue,LPCSTR pDfltVal) const
{
	const xbXmlFlag *flg = getFlag(name);
	if (!flg || !flg->m_bValue) 
	{
		if (pValue && pDfltVal) *pValue = pDfltVal;
		return false;
	}
	
	if (pValue) *pValue = flg->GetUnescValue();
	
	return true;
}

const xbXmlFlag *xbXmlParser::getFlag(const xbString &name) const
{
	checkInit();
	const xbXmlFlag *ptr = m_Flags.Begin();
	const xbXmlFlag *pend = m_Flags.End();
	while(ptr < pend)
	{
		if (Compare(name,ptr->GetUnescName()) == 0) return ptr;
		ptr++;
	}
	return NULL;
}

void xbXmlParser::warning(const xbString &msg,int col,int line,LPCSTR pCtx)
{
	if (col < 0) col = m_col;
	if (line < 0) line = m_line;
	if (m_prTarget && m_prTarget->Warning(m_FileName,col,line,msg,pCtx))
		return;
	if (!g_xbExceptionEngine.IsToLog("xbExmlParser"))
		return;
	xbRelOutput(m_FileName + "(" + xbString(line) + ") : column = "+
		xbString(col)+", xbXmlParser::warning : "+msg);
}

void xbXmlParser::error(const xbString &msg,int col,int line,LPCSTR pCtx)
{
	if (col < 0) col = m_col;
	if (line < 0) line = m_line;
	if (m_prTarget && m_prTarget->Error(m_FileName,col,line,msg,pCtx)) 
		return;
	if (!g_xbExceptionEngine.IsToLog("xbExmlParser"))
		return;
	xbRelOutput(m_FileName + "(" + xbString(line) + ") : column = "+
		xbString(col)+", xbXmlParser::ERROR : "+msg);
}

void xbXmlParser::PushTarget(IxbXmlTarget *pTarget)
{
	checkInit();
	m_arrTarget.Add(m_prTarget);
	m_prTarget = pTarget;
}

IxbXmlTarget	*xbXmlParser::PopTarget(IxbXmlTarget *pTarget,xbOpMode opm)
{
	checkInit();
	if (pTarget && m_prTarget != (IUnknown*)pTarget) 
	{
		if (opm == xbOp_Required)
			xbThrow xbExmlParser("xbXmlParser::PopTarget() without its corresponding PushTarget()");
		return NULL;
	}
	int last = m_arrTarget.GetLength()-1;
	if (last < 0) 
	{
		if (m_prTarget.IsEmpty())
		{
			if (opm == xbOp_Required)
				xbThrow xbExmlParser("bXml::PopTarget() : too many end section");
			return NULL;
		}
		m_prTarget.Empty();
		return NULL;
	}
	m_prTarget = m_arrTarget.GetAt(last);
	if (last == 0) 
		m_arrTarget.Clear();
	else 
		m_arrTarget.SetLength(last);
	
	return m_prTarget;
}


void xbXmlParser::ParseEnd(bool bOk)
{
	xbRdbgAssert(m_pSectTextStart==NULL&&m_pSectTextEnd==NULL);
	m_pSectTextStart = NULL;
	m_pSectTextEnd = NULL;
	if (!m_bInit)
		return;
//	if (!m_prTarget) 
//		xbThrow xbExmlParser("xbXmlParser::ParseEnd("+m_FileName+") : Target not set");
	if (bOk)
	{
		if (m_chInStr != 0) 
		{
			if (m_bEscInStr)
				error("XML section '"+m_sect+"' / string not closed and escape sequence not terminated !",m_flag.m_bValue?m_flag.m_posValue:m_flag.m_posName);
			else error("XML section '"+m_sect+"' / string not closed !",m_flag.m_bValue?m_flag.m_posValue:m_flag.m_posName);
		}
		if (m_bInSection) 
		{
			error("XML section '"+m_sect+"' not closed !",m_sectPos);
			sectionFeed();
		}
		else textFeed();
		
		if (m_arrTarget.IsValid()) 
		{
			xbString ssect;
			if (m_prTarget.IsValid())
				ssect += m_prTarget->GetXmlLabel(this);
			else
				ssect += "??";
			for(int i = m_arrTarget.Length - 1; i >= 1;i--)
			{
				ssect += ","+m_arrTarget.GetAt(i)->GetXmlLabel(this);
			}

			error(xbString(m_arrTarget.GetLength())+" end section(s) </...> missing ("+ssect+")!" );
		}
	}

	while(m_prTarget.IsValid())
	{
		xmlProtect(m_prTarget->EndParse(this,bOk));
		PopTarget(NULL,xbOp_Optional);
	}

	m_arrTarget.Clear();
	
	m_bInit = false;

	m_prTarget.Release();
}

#pragma warning(pop)
