/*<<<***************************************************************************
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: xbExpr
#Project: xdcBase
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"

#include "xbExpr.h"
#include "xbXmlItf.h"
#include "xbDnItf.h"
#include "xbFileTools.h"

#pragma warning(push)
#pragma warning(disable: 4311)
////////////////////////////////////////////////////////////////////////////////////////////
// xbExpr
////////////////////////////////////////////////////////////////////////////////////////////
bool xbExpr::SearchExpr(const xbString &str,ulong mode)
{
	return parseSearchExpr(str,mode);
}

bool xbExpr::SearchString(const xbString &str,ulong mode)
{
	return makeSearchString(str,mode);
}

bool xbExpr::SearchSubString(const xbString &str,ulong mode)
{
	return makeSearchSubString(str,mode);
}

bool xbExpr::SearchNext()
{
	for(;;)
	{
		if (!onGetNext()) return false;
		if (!m_cond.IsOk(this,m_mode)) continue;
		return true;
	}
	return false;
}

bool xbExpr::Cond::IsOk(xbExpr *pExpr,ulong mode) const
{
	bool ret = IsKeysOk(pExpr,mode);
	if (m_bNot) ret = !ret;
	if (!ret) return false;


	Cond*const *ptr = m_conds.Begin();
	if (!ptr) return true;

	Cond*const *pend = m_conds.End();

	ret = !m_bOr;
	while(ptr < pend)
	{
		bool sret = (*ptr)->IsOk(pExpr,mode);
		if (m_bOr)
		{
			if (sret) {ret = true; break;}
		}
		else
		{
			if (!sret) {ret = false; break;}
		}
		ptr++;
	}

	if (m_bNot) ret = !ret;
	
	return ret;
}

bool xbExpr::Cond::IsKeysOk(xbExpr *pExpr,ulong mode) const
{
	if (m_keys.IsEmpty()) return true;
	if (m_mode != 0) mode = m_mode;

	return pExpr->onKeysOk(this,mode);
}

bool xbExpr::Cond::IsKeyOk(const xbString &str,ulong mode) const
{
	const CondKey	*ptr = m_keys.Begin();
	const CondKey	*pend = m_keys.End();

	const char *pStr = str;
	while(ptr < pend)
	{
		if (!ptr->IsOk(pStr,mode,&pStr)) return false;
		ptr++;
	}

	return true;
}

bool xbExpr::CondKey::IsOk(const char *pStr,ulong mode,const char **ppStr) const
{
	if (m_bStrStart && m_bStrEnd)
	{
		bool ret;
		if (mode & Expr_CaseDep) ret = xbStrcmp(pStr,m_key) == 0;
		else ret = xbStricmp(pStr,m_key) == 0;
		if (!ret) return false;
		*ppStr = NULL;
		return true;
	}
	if (m_bStrStart && !m_bStrEnd)
	{
		int lg = m_key.GetLength();
		bool ret;
		if (mode & Expr_CaseDep) ret = xbStrncmp(pStr,m_key,lg) == 0;
		else ret = xbStrnicmp(pStr,m_key,lg) == 0;
		if (!ret) return false;
		*ppStr = pStr + lg;
		return true;
	}
	if (!m_bStrStart && m_bStrEnd)
	{
		int lg = m_key.GetLength();
		const char *ptr = pStr + strlen(pStr) - lg;
		if (ptr < pStr) return false;
		bool ret;
		if (mode & Expr_CaseDep) ret = xbStrcmp(ptr,m_key) == 0;
		else ret = xbStricmp(ptr,m_key) == 0;
		if (!ret) return false;
		*ppStr = NULL;
		return true;
	}
	int lg = m_key.GetLength();
	if (lg <= 0)
	{
		*ppStr = NULL;
		return true;
	}
	int cnt = int(strlen(pStr));
	while(*pStr && cnt >= lg)
	{
		bool ret;
		if (mode & Expr_CaseDep) ret = xbStrncmp(pStr,m_key,lg) == 0;
		else ret = xbStrnicmp(pStr,m_key,lg) == 0;
		if (ret)
		{
			*ppStr = pStr + lg;
			return true;
		}
		pStr++;
		cnt--;
	}
	return false;
}

bool xbExpr::parseSearchExpr(const xbString &searchstr,ulong mode)
{
	if (searchstr == m_searchStr && m_mode == mode && m_searchType == 2) return true;
	m_searchType = 2;
	m_searchStr = searchstr;
	m_mode = mode;
	m_cond.init();
	eProtectBegin();
	try
	{
		const char *pStr = m_searchStr;
		parseSearchExpr(&m_cond,mode,&pStr,0);
		m_cond.Dump(0);
		return true;
	}
	catch(...)
	{
		m_cond.Dump(0);
		m_searchStr.Empty();
		m_cond.init();
		m_mode = Expr_All;
		m_searchType = 0;
		throw;
	}
	eProtectEndTrace();
	return false;
}

void xbExpr::parseSearchExpr(Cond *pCond, ulong mode, const char **ppStr, char chStop)
{
	const char *psearch = *ppStr;
	Cond *pCurCond = pCond;
	CondKey	key;
	for(;;)
	{
		char ch = *(psearch++);
		if (ch == '#')
		{
			ch = *(psearch++);
			switch(ch)
			{
			case 0 :
				xbThrowEsys("xbExpr::parseSearchExpr("+xbString(*ppStr)+") : unexpected # at end of string");
			default:
				if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
				{
					int n = ch;
					if (ch >= '0' && ch <= '9') n -= '0';
					if (ch >= 'A' && ch <= 'F') n -= 'A' + 10;
					else n-= 'a' + 10;
					ch = *psearch;
					if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f'))
					{
						psearch++;
						n <<= 4;
						if (ch >= '0' && ch <= '9') n += ch - '0';
						if (ch >= 'A' && ch <= 'F') n += ch - 'A' + 10;
						else n += ch - 'a' + 10;
					}
					if (n == 0)
						xbThrowEsys("xbExpr::parseSearchExpr("+xbString(*ppStr)+") : string to search can't contain null character (#0) !");
				}
				key.m_key += ch;
				break;
			}
			continue;
		}
		if (!ch || ch == chStop || ch == '*' || ch == '&' || ch == '|' || ch == '(')
		{
			if ((ch == '*' || !key.m_key.IsEmpty()) && !pCurCond->m_conds.IsEmpty())
				xbThrowEsys("xbExpr::parseSearchExpr("+xbString(*ppStr)+") : unexpected character '"+xbStringPad(1,ch)+"', kw='"+key.m_key+"' !");

			if (!key.m_key.IsEmpty())
			{
				if (ch == '*') key.m_bStrEnd = false;
				pCurCond->m_keys.Add(key);
				key.init();
				if (ch == '*') key.m_bStrStart = false;
			}
			else
			{
				if (ch == '*') key.m_bStrStart = false;
			}
			if (ch == '*') continue;
		}
		if (!ch)
		{
			if (chStop) 
				xbThrowEsys("xbExpr::parseSearchExpr("+xbString(*ppStr)
				+") : unexpected end of string, '"+xbStringPad(1,chStop)+"' omitted !");
			break;
		}
		if (ch == chStop) break;
		if (ch == '(')
		{
			Cond	*pNewCond = new Cond();
			try
			{
				parseSearchExpr(pNewCond,mode,&psearch,')');
			}
			catch(...)
			{
				delete pNewCond;
				throw;
			}
			pCurCond->m_conds.Add(pNewCond);
			continue;
		}
		if (ch == '&' || ch == '|')
		{
			if (pCond->m_conds.IsEmpty())
			{
				if (pCond->m_keys.IsEmpty()) continue;
				Cond	*pNewCond = new Cond();
				try
				{
					pNewCond->m_keys = pCond->m_keys;
					pCond->m_keys.Clear();
					pNewCond->m_mode = pCond->m_mode;
					pCond->m_mode = 0;
					pNewCond->m_bNot = pCond->m_bNot;
					pCond->m_bNot = false;
				}
				catch(...)
				{
					delete pNewCond;
					throw;
				}
				pCond->m_conds.Add(pNewCond);
				pCond->m_bOr = (ch == '|');
			}
			else 
			{
				if (pCond->m_conds.GetLength() > 1 
					&& ((ch == '&' && pCond->m_bOr) || (ch == '|' && !pCond->m_bOr)))
				{
					Cond	*pNewCond = new Cond();
					try
					{
						pNewCond->m_keys = pCond->m_keys;
						pCond->m_keys.Clear();
						pNewCond->m_conds.CopyPointersFrom(pCond->m_conds);
						pCond->m_conds.ClearWithoutDelete();
						pNewCond->m_mode = pCond->m_mode;
						pCond->m_mode = 0;
						pNewCond->m_bNot = pCond->m_bNot;
						pCond->m_bNot = false;
					}
					catch(...)
					{
						delete pNewCond;
						throw;
					}
					pCond->m_conds.Add(pNewCond);
					pCond->m_bOr = (ch == '|');
				}
				else
				{
					pCond->m_bOr = (ch == '|');
					Cond	**pLastCond = pCond->m_conds.End() - 1;
					if (*pLastCond == pCurCond && (*pLastCond)->m_keys.IsEmpty())
					{
						(*pLastCond)->m_mode = 0;
						continue;
					}
				}
			}

			Cond	*pNewCond = new Cond();
			pCond->m_conds.Add(pNewCond);
			pCurCond = pNewCond;
			continue;
		}
		if (ch == ':' && !key.m_key.IsEmpty() && pCurCond->m_keys.IsEmpty() && pCurCond->m_conds.IsEmpty())
		{
			xbString str(key.m_key);
			key.m_key.Empty();
			int op = 0;
			if (str.First() == '+') {op = 1;str.Delete(0);}
			else if (str.First() == '-') {op = -1;str.Delete(0);}
			bool bFirst = false;
			if (pCurCond->m_mode == 0) 
			{
				pCurCond->m_mode = mode;
				bFirst = true;
			}

			if (onFlagSet(bFirst,str,op,&pCurCond->m_mode)) continue;

			if (str.CompareNoCase("all") == 0)
			{
				if (op == 1) pCurCond->m_mode |= Expr_All;
				else if (op == -1) pCurCond->m_mode &= ~Expr_All;
				else 
				{
					pCurCond->m_mode |= Expr_All;
				}
				continue;
			}
			if (str.CompareNoCase("case") == 0)
			{
				if (op == -1) pCurCond->m_mode &= ~Expr_CaseDep;
				else pCurCond->m_mode |= Expr_CaseDep;
				continue;
			}
			if (str.CompareNoCase("icase") == 0)
			{
				if (op == -1) pCurCond->m_mode |= Expr_CaseDep;
				else pCurCond->m_mode &= ~Expr_CaseDep;
				continue;
			}
			xbThrowEsys("xbExpr::parseSearchExpr("+xbString(*ppStr)+") : unknown flag mode '"+str+"'");
		}
		if (ch == '!' && key.m_key.IsEmpty() && pCurCond->m_keys.IsEmpty() && pCurCond->m_conds.IsEmpty())
		{
//			if (pCurCond->m_bNot)
//				xbThrowEsys("xbExpr::parseSearchExpr("+xbString(*ppStr)+") : unexpected character '!'");
			pCurCond->m_bNot = true;
			continue;
		}

		key.m_key += ch;
	}
	*ppStr = psearch;
}

void xbExpr::Cond::Dump(int level) const
{
#ifdef _DEBUG
	xbString spc(xbStringPad(level * 4));
	xbString kd;
	{
		const CondKey	*ptr = m_keys.Begin();
		const CondKey	*pend = m_keys.End();

		while(ptr < pend)
		{
			if (!kd.IsEmpty()) 
				kd += char('; ');
			if (!ptr->m_bStrStart) 
				kd += char('*');
			kd+= "'" + ptr->m_key + "'";
			if (!ptr->m_bStrEnd) 
				kd += char('*');
			ptr++;
		}
	}
	xbDbgOutput(spc+"- Cond group("+xbString(m_bNot?"NOT ":"")+xbString(m_bOr?"OR":"AND")+") : keys("+xbString(m_mode,xbStringNum_hexa)+")="+kd);
	Cond*const *ptr = m_conds.Begin();
	Cond*const *pend = m_conds.End();

	while(ptr < pend)
	{
		(*ptr)->Dump(level+1);
		ptr++;
	}

#endif
}

bool xbExpr::makeSearchString(const xbString &searchstr,ulong mode)
{
	if (searchstr == m_searchStr && m_mode == mode && m_searchType == 0) return true;
	m_searchType = 0;
	m_searchStr = searchstr;
	m_mode = mode;
	m_cond.init();
	eProtectBegin();
	try
	{
		CondKey key;
		key.m_key = searchstr;
		m_cond.m_keys.Add(key);
		m_cond.Dump(0);
		return true;
	}
	catch(...)
	{
		m_cond.Dump(0);
		m_searchStr.Empty();
		m_cond.init();
		m_mode = Expr_All;
		throw;
	}
	eProtectEndTrace();
	return false;

}

bool xbExpr::makeSearchSubString(const xbString &searchstr,ulong mode)
{
	if (searchstr == m_searchStr && m_mode == mode && m_searchType == 1) return true;
	m_searchType = 1;
	m_searchStr = searchstr;
	m_mode = mode;
	m_cond.init();
	eProtectBegin();
	try
	{
		CondKey key;
		key.m_key = searchstr;
		key.m_bStrEnd = key.m_bStrStart = false;
		m_cond.m_keys.Add(key);
		m_cond.Dump(0);
		return true;
	}
	catch(...)
	{
		m_cond.Dump(0);
		m_searchStr.Empty();
		m_cond.init();
		m_mode = Expr_All;
		m_searchType = 0;
		throw;
	}
	eProtectEndTrace();
	return false;

}

xbString xbConsoleTextClean(const xbString &str)
{
	xbString ret;
	const char *ptr = str;
	const char *ptrEnd = ptr + str.Length;
	ret.Length = str.Length;
	char *ptrDst = (char*)((const char*)ret);

	bool bCopy = true;
	while(ptr < ptrEnd)
	{
		if (*ptr == '\2')
		{
			bCopy = !bCopy;
			ptr++;
			continue;
		}
		if (bCopy)
		{
			*ptrDst = *ptr;
			ptrDst++;
		}
		ptr++;
	}

	ret.Length = (int)(xbNptr(ptrDst) - xbNptr((const char*)ret));

	return ret;
}

xbString xbHtmlRemove(const xbString &str,int maxChar,bool keepSimpleHtml,bool keepLine)
{
    if (!str)
        return "";

    int pos=0;
	char cfound;
	bool intag=false,inBold=false,inStrong=false,inItalic=false,inUnderline=false;
	xbString ret,token,sdel;
    while((pos=str.GetToken(pos,"<>",&token,NULL,&cfound,sdel,NULL,NULL,false))>0)
    {
		for(;;)
		{
			if (intag)
			{
				if (keepLine && token.CompareNoCase("br")==0)
				{
					if (keepSimpleHtml)
						ret += "<br>\n";
					else
						ret += "\n";
					break;
				}
				if (token.CompareNoCase("/p")==0)
				{
					ret += " ";
					break;
				}
				if (!keepSimpleHtml)
					break;
				if (token.CompareNoCase("b")==0)
				{
					ret += "<"+token+">";
					inBold = true;
					break;
				}
				if (token.CompareNoCase("/b")==0)
				{
					if (inBold)
						ret += "<"+token+">";
					inBold = false;
					break;
				}
				if (token.CompareNoCase("strong")==0)
				{
					ret += "<"+token+">";
					inStrong = true;
					break;
				}
				if (token.CompareNoCase("/strong")==0)
				{
					if (inStrong)
						ret += "<"+token+">";
					inStrong = false;
					break;
				}

				if (token.CompareNoCase("i")==0)
				{
					ret += "<"+token+">";
					inItalic = true;
					break;
				}
				if (token.CompareNoCase("/i")==0)
				{
					if (inItalic)
						ret += "<"+token+">";
					inItalic = false;
					break;
				}
				if (token.CompareNoCase("u")==0)
				{
					ret += "<"+token+">";
					inUnderline = true;
					break;
				}
				if (token.CompareNoCase("/u")==0)
				{
					if (inUnderline)
						ret += "<"+token+">";
					inUnderline = false;
					break;
				}
				break;
			}

			ret += xbHtmlUnescape(token);
            if (maxChar && ret.Length>maxChar)
            {
                if (inStrong)
                    ret += "</strong>";
                if (inBold)
                    ret += "</b>";
                if (inItalic)
                    ret += "</i>";
                if (inUnderline)
                    ret += "</u>";


                if (keepSimpleHtml)
                    ret += " <i>[...]</i>";
                else
                    ret += " [...]";

                return ret;
            }
			break;
        }
        if (cfound=='<')
        {
            sdel = "\"'";
            intag=true;
        }
        else
        {
            sdel.Empty();
            intag = false;
        }
    }

    if (inStrong)
        ret += "</strong>";
    if (inBold)
        ret += "</b>";
    if (inItalic)
        ret += "</i>";
    if (inUnderline)
        ret += "</u>";

    return ret;
}

xbString xbSubstEscape(const xbString &str)
{
	if (str.IsEmpty() || str.IndexOf("${") < 0)
		return str;

	return "${no('"+xbEscapeExt(str)+"')}";
}

xbString xbSubstEscape2(const xbString &str)
{
	xbString ret(str);
	int pos = 0;
	while((pos = ret.IndexOf("${",pos)) >= 0)
	{
		xbString token;
		int pend = ret.GetToken(pos+2,"}",&token,NULL,NULL,"\"'","{[(","}])");

		if (pend <= 0) 
			break;

		token = "${["+token+"]}";
		ret.Delete(pos,pend-pos);
		ret.Insert(pos,token);
		pos += token.Length;
	}
	return ret;
}

xbString xbSubstVar(sIxbSubst *pCb,LPCSTR pFmt,const xbString &str,LPCSTR pCtx,sIxbSubstTrace *pCbTrace,LPCSTR pDfltFmt)
{
	xbSubstExecSt ctx(pCb,pFmt,pCtx,pCbTrace,str,pDfltFmt);

	eCtxBegin();

	xbString ret(xbSubstVar(ctx,str));

	ctx.close();

	return ret;
	eCtxEndNoLoc(ctx.getEctx("Subst: expanding string"));
}

//${=html} ....
xbString xbSubstVar(xbSubstExecSt &ctx,const xbString &str)
{
	xbString result;

	xbEstack *pEstack = NULL;
	int lastpos = 0;
	try
	{
		int pos = 0;
		while((pos = str.IndexOf("${",pos)) >= 0)
		{
			xbString _token,token;
			int pend = str.GetToken(pos+2,"}",&token,NULL,NULL,"\"'","{[(","}])");
			_token = token;

			if (pend <= 0) 
			{
				if (pend == xbTokenError_StringNotTerminated)
				{
					ctx.throwError("String not terminated");
					break;
				}
				if (pend == xbTokenError_BlockNotTerminated)
				{
					ctx.throwError("Delimited block not terminated");
					break;
				}
				if (pend < xbTokenError_End)
				{
					ctx.throwError("Unexpected result for parse");
					break;
				}
				ctx.throwError("Token ${...} not terminated");
				break;
			}
			if (pos > lastpos && ctx.toExecute())
				result += ctx.convert(str.Mid(lastpos, pos - lastpos));

			pos = pend;
			lastpos = pend;

			if (ctx.toExecute() && token.IsValid())
			{
				if (ctx.m_pCb && token.First() == '[' && token.Last() == ']')
				{
					result += "${"+token.Mid(1,token.Length-2).RetTrim()+"}";
					continue;
				}
				if (token.First() == '=')
				{
					ctx.m_currentFmt = token.Mid(1);
					if (ctx.m_bKeepFmt)
					{
						result += "${"+token+"}";
						continue;
					}
					continue;
				}
			}
			xbString substOp,ctxOp;
			char chSep=0;
			int psep = token.GetToken(0,"§,?",&token,&substOp,&chSep,"\"'","{[(","}])");
			if (chSep == '§')
			{
				ctxOp = token;
				token = substOp;
				substOp.Empty();
				token.GetToken(0,",",&token,&substOp,&chSep,"\"'","{[(","}])");
			}
			if (chSep == '?')
			{
				xbString tokenKeep;
				substOp.GetToken(0,",",&tokenKeep,&substOp,NULL,"\"'","{[(","}])");
				token += "?"+tokenKeep;
			}

			if (!xbSubstVarExpand(ctx,token,ctxOp,substOp))
			{
				result += "${"+_token+"}";
				continue;
			}
			if (token.IsEmpty())
				continue;

			result += ctx.escape(token);
		}

		if (lastpos < str.Length && ctx.toExecute())
		{
			if (lastpos == 0)
				return ctx.convert(str);

			result += ctx.convert(str.Mid(lastpos));
			lastpos = str.Length;
		}
	}
	catch(xbEstack *pE)
	{
		pEstack = pE;
	}
	catch(xbEbase *pE)
	{
		try
		{
			if (ctx.m_pCb && ctx.m_pCb->onSubstErrorTrace(ctx, pE, str))
			{
				pE->Delete();
				pE = NULL;

				if (lastpos < str.Length && ctx.toExecute())
				{
					if (lastpos == 0)
						return ctx.convert(str);

					result += ctx.convert(str.Mid(lastpos));
				}
				return result;
			}
		}
		catch(...)
		{
			if (pE)
				pE->Delete();
			throw;
		}

		throw;
	}
	if (pEstack)
	{
		::xbProcessException(pEstack,false,true);
		throw pEstack;
	}
	return result;
}

//${no()}

bool xbSubstVarExpand(xbSubstExecSt &ctx,xbString &token,const xbString &ctxOp,const xbString &substOp)
{
	if (!ctx.m_pCb)
		return false;

	eCtxBegin();
	xbEstack *pEstack=NULL;
	try
	{
		if (token.CompareNoCase("endif") == 0)
		{
			if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
				return false;
			ctx.condPop();
			token.Empty();
			return true;
		}
		if (token.CompareNoCase("else") == 0)
		{
			if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
				return false;
			ctx.condInvert();
			token.Empty();
			return true;
		}
		if (token.CompareNoCase("fmt") == 0)
		{
			if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
				return false;
			token = ctx.m_pFmt;
			return true;
		}

		ctx.substVarStart();
		xbString subName,subArgs;
		if (ctx.isSubCall(token,&subName,&subArgs))
		{
			if (!ctx.toExecute())
			{
				if (subName.CompareNoCase("if") == 0)
				{
					if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
						return false;
					ctx.condPush(false);
					token.Empty();
					return true;
				}
				token.Empty();
				return true;
			}


			if (subName.CompareNoCase("no") == 0)
			{
				if (ctx.m_bKeepFmt)
					return false;

				if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
					return false;

				xbArray<xbString> arrArg;
				ctx.getArgs(subArgs,&arrArg,NULL);
				token.Empty();

				for(int i = 0; i < arrArg.Length; i++)
				{
					token += arrArg.GetAt(i);
				}

				goto substOpDo;
			}
			if (subName.CompareNoCase("str") == 0)
			{
				if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
					return false;

				xbArray<xbString> arrArg;
				ctx.getArgs(subArgs,&arrArg,NULL);
				token.Empty();

				for(int i = 0; i < arrArg.Length; i++)
				{
					token += arrArg.GetAt(i);
				}

				goto substOpDo;
			}
			if (subName.CompareNoCase("if") == 0)
			{
				if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
					return false;

				xbSubstExecSt _ctx(&ctx,token);
				ctx.condPush(xbSubstBoolEval(_ctx,subArgs));
				token.Empty();

				return true;
			}

			if (subName.CompareNoCase("?") == 0)
			{
				if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
					return false;

				xbArray<xbString> arrArg;
				if (ctx.getArgs(subArgs,&arrArg,NULL)>3)
					ctx.throwError("In command op?opTrue:opFalse too many ':'");

				xbSubstExecSt _ctx(&ctx,token);
				if (xbSubstBoolEval(_ctx,arrArg.First()))
				{
					if (arrArg.Length == 1)
					{
						token = "true";
						goto substOpDo;
					}
					token = arrArg.First();
					goto substOpDo;
				}
				if (arrArg.Length == 1)
				{
					token = "false";
					goto substOpDo;
				}
				if (arrArg.Length >= 3)
					token = arrArg.GetAt(2);
				else
					token.Empty();
				goto substOpDo;
			}
			if (subName.CompareNoCase("url") == 0)
			{
				if (ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
					return false;

				xbArray<xbString> arrArg;
				if (ctx.getArgs(subArgs,&arrArg,NULL)>3)
					ctx.throwError("In function url(url[,label[,hint]]), too many arguments");

				token.Empty();

				xbString lbl,hint;
				if (arrArg.Length > 1)
					lbl = arrArg.First();
				if (arrArg.Length > 2)
					hint = arrArg.GetAt(2);

				if (!ctx.m_pCb->onSubstUrlExpand(ctx,arrArg.First(),lbl,hint,&token))
					return false;

				if (token.IsEmpty() && lbl.IsValid())
					token = lbl;

				goto substOpDo;
			}

			xbArray<xbString> arrArg;
			ctx.getArgs(subArgs,&arrArg,NULL);

			if (!ctx.m_pCb->onSubstSubExpand(ctx,subName,arrArg,&token,ctxOp,xbOp_Required))
				return false;
		}
		else
		{
			if (!ctx.toExecute())
			{
				token.Empty();
				return true;
			}
			if (!ctx.m_pCb->onSubstVarExpand(ctx,token,&token,ctxOp,xbOp_Required))
				return false;
		}

	substOpDo:
		if (substOp.IsValid())
		{
			xbArray<xbString> arrArg;
			ctx.getArgs(substOp,&arrArg,",");

			xbSubstOperation(ctx, token, arrArg);
		}

		return true;
	}
	catch(xbEstack *pE)
	{
		pEstack = pE;
	}
	catch(xbEbase *pE)
	{
		try
		{
			if (substOp.IsValid())
			{
				xbArray<xbString> arrArg;
				eProtectTrace(ctx.getArgs(substOp,&arrArg,","););

				if (xbSubstOperationError(ctx, pE, arrArg))
				{
					pE->Delete();
					token.Empty();
					return true;
				}
			}
			if (ctx.m_pCb->onSubstErrorTrace(ctx, pE, token))
			{
				pE->Delete();
				return false;
			}
		}
		catch(...)
		{
			pE->Delete();
			throw;
		}

		throw;
	}
	if (pEstack)
	{
		::xbProcessException(pEstack,false,true);
		throw pEstack;
	}
	eCtxEndNoLoc(ctx.getEctx("Subst: expanding var",token));
	return false;
}

void xbSubstOperationArg(xbSubstExecSt &ctx,xbString &expr)
{
	if (expr.IsEmpty())
		return;

	if (expr.First()=='\'' || expr.First()=='"')
	{
		xbString ret;
		if (expr.GetTokenString(0,"\"'",&ret) <= expr.Length)
			ctx.throwError("Invalid quoted string");

		expr = ret;
	}
}

bool xbSubstOperationError(xbSubstExecSt &ctx,xbEbase *pE,const xbArray<xbString> &arrArg)
{
	for(int i = 0; i < arrArg.Length; i++)
	{
		xbString op(arrArg.GetAt(i));
		if (ctx.m_pCb->onSubstOperationError(ctx,op,pE))
			return true;

		if (op.IndexOf('=')<0)
		{
			if (op.CompareNoCase("noerror") == 0)
				return true;

			continue;
		}

		xbString opName,opVal;
		op.GetToken(0,"=",&opName,&opVal);

		if (opName.CompareNoCase("error") == 0)
		{
			xbSubstOperationArg(ctx,opVal);

			if (opVal.IsEmpty())
				return false;

			opVal = ctx.subst("raw",opVal,"text");

			if (opVal.IsEmpty())
				return false;

			xbThrow xbEuser(opVal);
		}
	}

	return false;
}

void xbSubstOperation(xbSubstExecSt &ctx,xbString &str,const xbArray<xbString> &arrArg)
{
	xbString safter,sbefore,sempty,ssing,splur,smult,snone,sone,smore;
	for(int i = 0; i < arrArg.Length; i++)
	{
		xbString op(arrArg.GetAt(i));
		if (ctx.m_pCb->onSubstOperation(ctx,op,str))
			continue;
		if (op.IndexOf('=')<0)
		{
			if (op.CompareNoCase("makedir")==0)
			{
				::xbMakeDir(str,true);
				continue;
			}
			if (op.CompareNoCase("tolongpath")==0)
			{
				int pos=0;
				xbString ret,spath;
				while((pos=str.GetToken(pos,"|",&spath))>0)
				{
					if (spath.IsValid())
						spath = ::xbGetLongPath(spath);
					if (ret.IsValid())
						ret += "|";
					ret += spath;
				}
				str = ret;
				continue;
			}
			if (op.CompareNoCase("tofullpath")==0)
			{
				int pos=0;
				xbString ret,spath;
				while((pos=str.GetToken(pos,"|",&spath))>0)
				{
					if (spath.IsValid())
						spath = ::xbGetFullPath(spath);
					if (ret.IsValid())
						ret += "|";
					ret += spath;
				}
				str = ret;
				continue;
			}
			if (op.CompareNoCase("touncpath")==0)
			{
				int pos=0;
				xbString ret,spath;
				while((pos=str.GetToken(pos,"|",&spath))>0)
				{
					if (spath.IsValid())
						spath = ::xbGetUncPath(spath,xbOp_Optional);
					if (ret.IsValid())
						ret += "|";
					ret += spath;
				}
				str = ret;
				continue;
			}
			if (op.CompareNoCase("tolocalpath")==0)
			{
				int pos=0;
				xbString ret,spath;
				while((pos=str.GetToken(pos,"|",&spath))>0)
				{
					if (spath.IsValid())
						spath = ::xbGetLocalPath(spath,xbOp_Optional);
					if (ret.IsValid())
						ret += "|";
					ret += spath;
				}
				str = ret;
				continue;
			}
			if (op.CompareNoCase("pathExpand")==0)
			{
				int pos=0;
				xbString ret,spath;
				while((pos=str.GetToken(pos,"|",&spath))>0)
				{
					if (spath.IsValid())
						spath = g_xbCfgEngine.GetExpandedString(spath);
					if (ret.IsValid())
						ret += "|";
					ret += spath;
				}
				str = ret;
				continue;
			}
			if (op.CompareNoCase("noencode")==0)
			{
				ctx.m_bEscape = false;
				continue;
			}
			if (op.CompareNoCase("hexa")==0)
			{
				ulong ul;
				const char *pchStop = NULL;
				if (xbStrToUlong(str,&ul,0,&pchStop)<=0
					|| (pchStop && *pchStop != 0))
					str.Empty();
				else
					str = xbString(ul,xbStringNum_hexa);
				continue;
			}
			if (op.CompareNoCase("subst")==0)
			{
				str = ctx.subst(NULL,str);
				continue;
			}
			if (op.CompareNoCase("substEscape")==0)
			{
				str = xbSubstEscape(str);
				continue;
			}
			if (op.CompareNoCase("upper")==0)
			{
				xbString old(str);
				::xbCPtransform(xbLCID_USER,old,old.Length,xbCPtransform_upper,&str);
				continue;
			}
			if (op.CompareNoCase("lower")==0)
			{
				xbString old(str);
				::xbCPtransform(xbLCID_USER,old,old.Length,xbCPtransform_lower,&str);
				continue;
			}
			if (op.CompareNoCase("unaccent")==0)
			{
				xbString old(str);
				::xbCPtransform(xbLCID_USER,old,old.Length,xbCPtransform_unaccent,&str);
				continue;
			}
			if (op.CompareNoCase("trim")==0)
			{
				str.Trim();
				continue;
			}
			if (op.CompareNoCase("firstCaseUpper")==0)
			{
				if (str.IsEmpty())
					continue;
				xbString old(str),str2;
				::xbCPtransform(xbLCID_USER,old,1,xbCPtransform_upper,&str);
				if (old.Length>1)
				{
					::xbCPtransform(xbLCID_USER,((LPCSTR)old)+1,old.Length - 1,xbCPtransform_lower,&str2);
					str += str2;
				}
				continue;
			}
			if (op.CompareNoCase("noerror")==0)
				continue;

			ctx.throwError("Substitution operation '"+op+"' unknown");
		}

		xbString opName,opVal;
		op.GetToken(0,"=",&opName,&opVal);
		xbSubstOperationArg(ctx,opVal);

		if (opName.CompareNoCase("tofirst")==0)
		{
			int pos = str.IndexOfNC(opVal);
			if (pos >= 0)
				str = str.Left(pos);
			continue;
		}
		if (opName.CompareNoCase("tolast")==0)
		{
			int pos = str.LastIndexOfNC(opVal);
			if (pos >= 0)
				str = str.Left(pos);
			continue;
		}
		if (opName.CompareNoCase("fromfirst")==0)
		{
			int pos = str.IndexOfNC(opVal);
			if (pos >= 0)
				str = str.Mid(pos+opVal.Length);
			else
				str.Empty();
			continue;
		}
		if (opName.CompareNoCase("fromlast")==0)
		{
			int pos = str.LastIndexOfNC(opVal);
			if (pos >= 0)
				str = str.Mid(pos+opVal.Length);
			else
				str.Empty();
			continue;
		}
		if (opName.CompareNoCase("encode")==0)
		{
			ctx.m_escapeMode = opVal;
			ctx.m_bEscape = true;
			continue;
		}
		if (opName.CompareNoCase("fmt")==0)
		{
			ctx.m_tokenFmt = opVal;
			ctx.m_bEscape = true;
			continue;
		}
		if (opName.CompareNoCase("subst")==0)
		{
			str = ctx.subst(NULL,str,opVal);
			continue;
		}
		if (opName.CompareNoCase("after")==0)
		{
			safter = opVal;
			continue;
		}
		if (opName.CompareNoCase("before")==0)
		{
			sbefore = opVal;
			continue;
		}
		if (opName.CompareNoCase("empty")==0)
		{
			sempty = opVal;
			continue;
		}
		if (opName.CompareNoCase("sing")==0)
		{
			ssing = opVal;
			continue;
		}
		if (opName.CompareNoCase("plur")==0)
		{
			splur = opVal;
			continue;
		}
		if (opName.CompareNoCase("none")==0)
		{
			snone = opVal;
			continue;
		}
		if (opName.CompareNoCase("one")==0)
		{
			sone = opVal;
			continue;
		}
		if (opName.CompareNoCase("mult")==0)
		{
			smult = opVal;
			continue;
		}
		if (opName.CompareNoCase("more")==0)
		{
			smore = opVal;
			continue;
		}
		if (opName.CompareNoCase("+")==0)
		{
			long nl,opl;
			const char *pchStop = NULL;
			if (xbStrToLong(opVal,&opl,0,&pchStop)<=0)
				ctx.throwError("Substitution operation '"+op+"': number expected as parameter for +=");
			if (xbStrToLong(str,&nl,0,&pchStop)<=0
				|| (pchStop && *pchStop != 0))
				str.Empty();
			else
				str = xbString(nl+opl);
			continue;
		}
		if (opName.CompareNoCase("-")==0)
		{
			long nl,opl;
			const char *pchStop = NULL;
			if (xbStrToLong(opVal,&opl,0,&pchStop)<=0)
				ctx.throwError("Substitution operation '"+op+"': number expected as parameter for -=");
			if (xbStrToLong(str,&nl,0,&pchStop)<=0
				|| (pchStop && *pchStop != 0))
				str.Empty();
			else
				str = xbString(nl-opl);
			continue;
		}
		if (opName.CompareNoCase("error")==0)
			continue;

		ctx.throwError("Substitution operation '"+op+"' unknown");
	}

	while (ssing.IsValid() || splur.IsValid() || sone.IsValid() || smult.IsValid())
	{
		int64 vnum=0;
		::xbStrToInt64(str,&vnum,0,NULL);
		if (vnum < 0)
		{
			vnum = -vnum;
			if (smore.IsValid())
				sbefore=smore+" ";
			else
				sbefore="more than ";
		}
		if (vnum == 0 && snone.IsValid())
		{
			str = ctx.subst(NULL,snone);
			return;
		}
		if (vnum > 1)
		{
			if (smult.IsValid())
			{
				str = ctx.subst(NULL,smult);
				return;
			}
			if (splur.IsEmpty())
				str = xbString(vnum)+" "+ssing+"s";
			else
				str = xbString(vnum)+" "+splur;
            break;
		}
		if (sone.IsValid())
		{
			str = ctx.subst(NULL,sone);
			return;
		}

		if (ssing.IsValid())
            str = xbString(vnum)+" "+ssing;
        break;
	}
		
	if (str.IsEmpty())
	{
		if (sempty.IsValid())
		{
			str = ctx.subst(NULL,sempty);
			return;
		}
		if (snone.IsValid())
		{
			str = ctx.subst(NULL,snone);
			return;
		}
		return;
	}

	if (safter.IsValid())
		str += ctx.subst(NULL,safter);
    if (sbefore.IsValid())
        str = ctx.subst(NULL,sbefore) + str;
}

bool xbSubstExecSt::isSubCall(const xbString &token,xbString *pSubName,xbString *pSubArgs)
{
	char chSep=0;
	int pos = token.GetToken(0," (?,:[{<>=!\"'+-*/.",NULL,NULL,&chSep);
	if (pos>0)
	{
		if (chSep == '(' && token.Last()==')')
		{
			if (pSubName)
				*pSubName = token.Left(pos-1);
			if (pSubArgs)
				*pSubArgs = token.Mid(pos,token.Length - pos - 1);
			m_pSepArg = ",";
			return true;
		}
		if (chSep == ' ')
		{
			if (pSubName)
				*pSubName = token.Left(pos-1);
			if (pSubArgs)
				*pSubArgs = token.Mid(pos);
			m_pSepArg = " ";
			return true;
		}
		if (chSep == '?')
		{
			if (pSubName)
				*pSubName = "?";
			if (pSubArgs)
			{
				if (pos >= token.Length)
					*pSubArgs = token.Left(pos-1);
				else
					*pSubArgs = token.Left(pos-1)+":"+token.Mid(pos);
			}

			m_pSepArg = ":";
			return true;
		}
	}

	chSep = 0;
	pos = token.GetToken(pos,"?",NULL,NULL,&chSep,"\"'","{[(","}])");
	if (chSep == '?')
	{
		if (pSubName)
			*pSubName = "?";
		if (pSubArgs)
		{
			if (pos >= token.Length)
				*pSubArgs = token.Left(pos-1);
			else
				*pSubArgs = token.Left(pos-1)+":"+token.Mid(pos);
		}

		m_pSepArg = ":";
		return true;
	}

	return false;
}

bool xbSubstExecSt::toExecute() const
{
	const bool *pend = m_arrCond.End();
	if (!pend)
		return true;
	return *(pend-1);
}
void xbSubstExecSt::condPop()
{
	if (m_arrCond.IsEmpty())
		throwError("Unexpected endif");
	m_arrCond.Length = m_arrCond.Length-1;
}
void xbSubstExecSt::condPush(bool bVal)
{
	m_arrCond.Add(bVal);
}
void xbSubstExecSt::condInvert()
{
	bool *pend = m_arrCond.End();
	if (!pend)
		throwError("Unexpected else");
	if (m_arrCond.Length>1)
	{
		if (!*(pend - 2))
			return;
	}
	
	*(pend-1) = !*(pend-1);
}

int xbSubstExecSt::getArgs(const xbString &strArg, xbArray<xbString> *pArray, LPCSTR pSeps)
{
	if (!pSeps)
		pSeps = m_pSepArg;
	return strArg.consoleGetArg(m_cmds,pArray,xbOp_Required,pSeps);
}

//extern xbString consoleTextClean(const xbString &str);
xbString xbSubstExecSt::convert(const xbString &str,LPCSTR pSrcFmt)
{
	if (!m_pFmt)
		return str;

	xbString srcFmt(pSrcFmt?pSrcFmt:m_currentFmt);
	if (srcFmt.IsEmpty() || srcFmt.CompareNoCase(m_pFmt) == 0)
		return str;

	if (srcFmt.CompareNoCase("text") == 0)
	{
		if (xbStricmp(m_pFmt,"html")==0 || xbStricmp(m_pFmt,"xhtl")==0 || xbStricmp(m_pFmt,"chtl")==0)
		{
			return xbHtmlEscapeLf(str);
		}
		if (xbStricmp(m_pFmt,"console")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"url")==0)
		{
			return xbUrlEscape(str,NULL,NULL,m_urlEscCP);
		}
		if (xbStricmp(m_pFmt,"script")==0)
		{
			return xbEscapeExt(str);
		}

		goto invalidCvt;
	}

	if (srcFmt.CompareNoCase("console") == 0)
	{
		if (xbStricmp(m_pFmt,"html")==0 || xbStricmp(m_pFmt,"xhtl")==0 || xbStricmp(m_pFmt,"chtl")==0)
		{
			return xbHtmlEscapeLf(xbConsoleTextClean(str));
		}
		if (xbStricmp(m_pFmt,"text")==0)
		{
			return xbConsoleTextClean(str);
		}
		if (xbStricmp(m_pFmt,"url")==0)
		{
			return xbUrlEscape(str,NULL,NULL,m_urlEscCP);
		}
		if (xbStricmp(m_pFmt,"script")==0)
		{
			return xbEscapeExt(str);
		}
		
		goto invalidCvt;
	}
	if (srcFmt.CompareNoCase("chtl") == 0)
	{
		if (xbStricmp(m_pFmt,"html")==0 || xbStricmp(m_pFmt,"xhtl")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"text")==0 || xbStricmp(m_pFmt,"console")==0)
		{
			return xbHtmlRemove(str,0,false,true);
		}
		if (xbStricmp(m_pFmt,"url")==0)
		{
			return xbUrlEscape(str,NULL,NULL,m_urlEscCP);
		}
		if (xbStricmp(m_pFmt,"script")==0)
		{
			return xbEscapeExt(str);
		}
		
		goto invalidCvt;
	}
	if (srcFmt.CompareNoCase("html") == 0 || srcFmt.CompareNoCase("xhtl") == 0 || srcFmt.CompareNoCase("chtl") == 0)
	{
		if (xbStricmp(m_pFmt,"chtl")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"html")==0 || xbStricmp(m_pFmt,"xhtl")==0)
		{
			return xbHtmlRemove(str,0,true,true);
		}
		if (xbStricmp(m_pFmt,"text")==0 || xbStricmp(m_pFmt,"console")==0)
		{
			return xbHtmlRemove(str,0,false,true);
		}
		if (xbStricmp(m_pFmt,"url")==0)
		{
			return xbUrlEscape(str,NULL,NULL,m_urlEscCP);
		}
		if (xbStricmp(m_pFmt,"script")==0)
		{
			return xbEscapeExt(str);
		}
		
		goto invalidCvt;
	}
	if (srcFmt.CompareNoCase("url") == 0)
	{
		if (xbStricmp(m_pFmt,"text")==0)
		{
			return xbUrlUnescape(str);
		}
		if (xbStricmp(m_pFmt,"console")==0)
		{
			return xbUrlUnescape(str);
		}
		if (xbStricmp(m_pFmt,"html")==0 || xbStricmp(m_pFmt,"xhtl")==0 || xbStricmp(m_pFmt,"chtl")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"script")==0)
		{
			return xbEscapeExt(xbUrlUnescape(str));
		}
		
		goto invalidCvt;
	}
	if (srcFmt.CompareNoCase("script") == 0)
	{
		if (xbStricmp(m_pFmt,"text")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"console")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"html")==0 || xbStricmp(m_pFmt,"xhtl")==0 || xbStricmp(m_pFmt,"chtl")==0)
		{
			return str;
		}
		if (xbStricmp(m_pFmt,"url")==0)
		{
			return xbUrlEscape(str,NULL,NULL,m_urlEscCP);
		}
		
		goto invalidCvt;
	}
invalidCvt:
	throwError("No conversion available from "+srcFmt+" to "+xbString(m_pFmt));
	return xbString(); //never executed: for compilo
}

xbString xbSubstExecSt::escape(const xbString &str)
{
	if (!m_bEscape)
		return str; //convert(str);

	LPCSTR pFmt;
	if (m_escapeMode.IsValid())
		pFmt = m_escapeMode;
	else
	{
		if (m_tokenFmt.IsValid())
		{
			if (!m_bKeepFmt)
				return convert(str,m_tokenFmt);

			if (m_currentFmt.CompareNoCase(m_tokenFmt) == 0)
				return str;

			return "${="+m_tokenFmt+"}"+str+"${="+m_currentFmt+"}";
		}
		pFmt = m_pFmt;
	}
	if (!pFmt)
	{
		if (m_currentFmt.IsEmpty())
			throwError("Undetermined output format, can't escape token");
		pFmt = m_currentFmt;
	}
	if (xbStricmp(pFmt,"script")==0)
	{
		return xbEscapeExt(str);
	}
	if (xbStricmp(pFmt,"html")==0 || xbStricmp(pFmt,"xhtl")==0 || xbStricmp(pFmt,"chtl")==0)
	{
		return xbHtmlEscapeLf(str);
	}
	if (xbStricmp(pFmt,"htmlnolf")==0 || xbStricmp(pFmt,"xhtlnolf")==0 || xbStricmp(pFmt,"chtlnolf")==0)
	{
		return xbHtmlEscape(str);
	}
	if (xbStricmp(pFmt,"text")==0 || xbStricmp(pFmt,"console")==0)
	{
		return str;
	}
	if (xbStricmp(pFmt,"url")==0)
	{
		return ::xbUrlEscape(str,NULL,NULL,m_urlEscCP);
	}
	if (xbStricmp(pFmt,"basicquoted")==0)
	{
		xbString ret(str);
		ret.Replace("\"","\"\"");
		return "\""+ret+"\"";
	}

	throwError("Unkown format ("+xbString(pFmt)+")");
	return xbString(); //never executed: for compilo
}

xbString xbSubstExecSt::subst(LPCSTR pFmt,const xbString &str,LPCSTR pFmtSrc)
{
	xbSubstExecSt ctx(this,str);

	if (pFmt)
	{
		if (xbStricmp(pFmt,"raw") == 0)
		{
			ctx.m_pFmt = NULL;
			ctx.m_bKeepFmt = true;
		}
		else
		{
			ctx.m_pFmt = pFmt;
			ctx.m_bKeepFmt = false;
		}
	}
	if (pFmtSrc)
		ctx.m_currentFmt = pFmtSrc;

	eCtxBegin();

	xbString ret(xbSubstVar(ctx,str));

	ctx.close();

	return ret;

	eCtxEndNoLoc(ctx.getEctx("Subst: expanding sub string"));
}

void xbSubstExecSt::throwError(const xbString &str)
{
	xbThrow xbEarg("Syntax error in text screen or command !\n\nReason: "+str);
}

xbString xbSubstExecSt::getEctx(LPCSTR pStep, LPCSTR pToken)
{
	xbString ret(pStep);

	xbString tok(pToken && *pToken?pToken:m_cmds);
	if (tok.Length > 200)
		tok = xbSubstEscape(tok.Left(200))+"[...]";
	else
		tok = xbSubstEscape(tok);

	if (pToken && *pToken)
		ret += "${=text}: in token '"+tok+"'";
	else
		ret += "${=text}: in string:\n"+tok;

	return ret;
}

void xbSubstExecSt::close()
{
	if (m_arrCond.IsValid())
		throwError(xbString(m_arrCond.Length)+" 'if' not closed");
	m_arrCond.Empty();
}

bool xbSubstExecSt::ctxValid(const xbString &ctxOp)
{
	if (ctxOp.IsValid())
	{
		if (!m_pCtx || !*m_pCtx)
			return false;
		if (ctxOp.CompareNoCase(m_pCtx)!=0)
			return false;
	}

	return true;
}

void xbSubstExec(sIxbSubst *pCb,const xbString &cmds,LPCSTR pCtx,sIxbSubstTrace *pCbTrace,bool bSubst)
{
	xbSubstExecSt ctx(pCb,"script",pCtx,pCbTrace,cmds,NULL);

	eCtxBegin();

	xbSubstExec(ctx,cmds,bSubst);

	ctx.close();

	eCtxEndNoLoc(ctx.getEctx("Subst: executing commands"));
}

void xbSubstExec(xbSubstExecSt &ctx,const xbString &cmds,bool bSubst)
{
	if (cmds.IsEmpty())
		return;

	xbString str;
	int pos = 0;
	while((pos = cmds.GetToken(pos,";\n",&str,NULL,NULL,"\"'","{[(","}])")) > 0)
	{
		if (str.IsEmpty())
			continue;
		if (str.First()=='{')
		{
			if (str.Last()!='}')
				ctx.throwError("Missing '}'");

			xbSubstExec(ctx,str.Mid(1,str.Length-2).RetTrim(),bSubst);
			continue;
		}
		if (bSubst && ctx.toExecute())
		{
			str = ctx.subst(NULL,str); //xbSubstVar(ctx,str);
			if (str.IsEmpty())
				continue;
		}
		xbString cmdName,cmdArgs;
		str.GetToken(0," ",&cmdName,&cmdArgs,NULL,"\"'","{[(","}])");

		if (!xbSubstExecCmd(ctx,cmdName,cmdArgs))
			ctx.throwError("Command("+cmdName+") invalid");
	}

	if (pos < xbTokenError_End) 
	{
		if (pos == xbTokenError_StringNotTerminated)
			ctx.throwError("String not terminated");
		if (pos == xbTokenError_BlockNotTerminated)
			ctx.throwError("Delimited block not terminated");
		ctx.throwError("Unexpected result for parse");
	}
}

bool xbSubstExecCmd(xbSubstExecSt &ctx,const xbString &cmdName,const xbString &cmdArgs)
{
	eCtxBegin();
	if (cmdName.CompareNoCase("else") == 0)
	{
		if (cmdArgs.IsValid())
			return false;
		ctx.condInvert();
		return true;
	}
	if (cmdName.CompareNoCase("endif") == 0)
	{
		if (cmdArgs.IsValid())
			return false;
		ctx.condPop();
		return true;
	}
	if (!ctx.toExecute())
	{
		if (cmdName.CompareNoCase("if") == 0)
		{
			ctx.condPush(false);
			return true;
		}
		return true;
	}
	if (cmdName.CompareNoCase("if") == 0)
	{
		xbSubstExecSt _ctx(&ctx,cmdArgs);
		ctx.condPush(xbSubstBoolEval(ctx,cmdArgs));
		return true;
	}

	return ctx.m_pCb->onSubstExecute(ctx,cmdName,cmdArgs);
	eCtxEndNoLoc(ctx.getEctx("Subst: executing command",cmdName+" "+cmdArgs));
	return false;
}

bool xbSubstBoolEval(xbSubstExecSt &ctx,const xbString &expr)
{
	if (expr.IsEmpty())
		ctx.throwError("Void expression");

	eCtxBegin();
	xbString str;
	int pos = 0;
	bool bRet = false;
	while((pos = expr.GetToken(pos,"|",&str,NULL,NULL,"\"'","{[(","}])")) > 0)
	{
		if (xbSubstBoolEval_And(ctx,str))
			bRet = true;
	}
	if (pos < xbTokenError_End) 
	{
		if (pos == xbTokenError_StringNotTerminated)
			ctx.throwError("String not terminated");
		if (pos == xbTokenError_BlockNotTerminated)
			ctx.throwError("Delimited block not terminated");
		ctx.throwError("Unexpected result for parse");
	}
	return bRet;
	eCtxEndNoLoc(ctx.getEctx("Subst: evaluating expression",expr));
	return false;
}

bool xbSubstBoolEval_And(xbSubstExecSt &ctx,const xbString &expr)
{
	if (expr.IsEmpty())
		ctx.throwError("Void expression");

	xbString str;
	int pos = 0;
	while((pos = expr.GetToken(pos,"&",&str,NULL,NULL,"\"'","{[(","}])")) > 0)
	{
		if (!xbSubstBoolEval_Operator(ctx,str))
			return false;
	}
	if (pos < xbTokenError_End) 
	{
		if (pos == xbTokenError_StringNotTerminated)
			ctx.throwError("String not terminated");
		if (pos == xbTokenError_BlockNotTerminated)
			ctx.throwError("Delimited block not terminated");
		ctx.throwError("Unexpected result for parse");
	}
	return true;
}

bool xbSubstBoolEval_Operator(xbSubstExecSt &ctx,const xbString &expr)
{
	if (expr.IsEmpty())
		ctx.throwError("Void expression");
	if (expr.First()=='!')
	{
		return !xbSubstBoolEval_Operator(ctx,expr.Mid(1).RetTrim());
	}
	if (expr.First()=='(')
	{
		if (expr.Last()!=')')
			ctx.throwError("Missing ')'");


		return xbSubstBoolEval(ctx,expr.Mid(1,expr.Length-2).RetTrim());
	}

	xbString op1,op2;
	char chOp;
	int pos = expr.GetToken(0,"=!<>~",&op1,&op2,&chOp,"\"'","{[(","}])");
	if (pos <= 0) 
	{
		if (pos == xbTokenError_StringNotTerminated)
			ctx.throwError("String not terminated");
		if (pos == xbTokenError_BlockNotTerminated)
			ctx.throwError("Delimited block not terminated");
		ctx.throwError("Unexpected result for parse");
	}
	if (chOp == 0)
	{
		xbString ret(xbSubstBoolEval_Operand(ctx,op1,xbOp_Optional));
		return ret.IsValid()&&ret.CompareNoCase("false")!=0&&ret.CompareNoCase("0")!=0;
	}
	if (chOp == '=')
	{
		xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
		xbString ret2(xbSubstBoolEval_Operand(ctx,op2));
		return xbSubstBoolEval_Compare(ret1,ret2)==0;
	}
	if (chOp == '!' && op2.IsValid() && op2.First() == '=')
	{
		xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
		xbString ret2(xbSubstBoolEval_Operand(ctx,op2.Mid(1).RetTrim()));
		return xbSubstBoolEval_Compare(ret1,ret2)!=0;
	}
	if (chOp == '~')
	{
		xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
		xbString ret2(xbSubstBoolEval_Operand(ctx,op2));
		return ::xbMatchPath(ret2,ret1)==0;
	}
	if (chOp == '!' && op2.IsValid() && op2.First() == '~')
	{
		xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
		xbString ret2(xbSubstBoolEval_Operand(ctx,op2.Mid(1).RetTrim()));
		return ::xbMatchPath(ret2,ret1)!=0;
	}
	if (chOp == '<')
	{
		if (op2.IsValid() && op2.First() == '=')
		{
			xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
			xbString ret2(xbSubstBoolEval_Operand(ctx,op2.Mid(1).RetTrim()));
			return xbSubstBoolEval_Compare(ret1,ret2)<=0;
		}
		xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
		xbString ret2(xbSubstBoolEval_Operand(ctx,op2));
		return xbSubstBoolEval_Compare(ret1,ret2)<0;
	}
	if (chOp == '>')
	{
		if (op2.IsValid() && op2.First() == '=')
		{
			xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
			xbString ret2(xbSubstBoolEval_Operand(ctx,op2.Mid(1).RetTrim()));
			return xbSubstBoolEval_Compare(ret1,ret2)>=0;
		}

		xbString ret1(xbSubstBoolEval_Operand(ctx,op1));
		xbString ret2(xbSubstBoolEval_Operand(ctx,op2));
		return xbSubstBoolEval_Compare(ret1,ret2)>0;
	}

	op2.Insert(0,chOp);
	ctx.throwError("Invalid operator ("+op2+")");
	return false; //never executed: for compilo
}

int xbSubstBoolEval_Compare(const xbString &ret1,const xbString &ret2)
{
	if (ret1.IsValid() && ret2.IsValid())
	{
		char ret1ch = ret1.First();
		char ret2ch = ret2.First();
		if ((ret1ch >= '0' && ret1ch <= '9') 
			&& (ret2ch >= '0' && ret2ch <= '9') )
		{
			const char *pchStop=NULL;
			if (ret1.IndexOf('.')>=0 || ret2.IndexOf('.')>=0)
			{
				double d1=::strtod(ret1,(char**)&pchStop);
				if (pchStop && *pchStop)
					goto strCmp;

				double d2=::strtod(ret2,(char**)&pchStop);
				if (pchStop && *pchStop)
					goto strCmp;

				if (d1 > d2)
					return 1;
				if (d1 < d2)
					return -1;
				return 0;
			}
			int64 n1,n2;
			::xbStrToInt64(ret1,&n1,0,&pchStop);
			if (pchStop && *pchStop)
				goto strCmp;
			::xbStrToInt64(ret2,&n2,0,&pchStop);
			if (pchStop && *pchStop)
				goto strCmp;
			if (n1 > n2)
				return 1;
			if (n1 < n2)
				return -1;
			return 0;
		}
	}

strCmp:
	return ret1.CompareNoCase(ret2);
}

xbString xbSubstBoolEval_Operand(xbSubstExecSt &ctx,const xbString &expr,xbOpMode opm)
{
	if (expr.IsEmpty())
		ctx.throwError("Void expression");

	xbString ret;
	if (expr.First()=='\'' || expr.First()=='"')
	{
		if (expr.GetTokenString(0,"\"'",&ret) <= expr.Length)
			ctx.throwError("Invalid quoted string");

		return ret;
	}

	if (!ctx.m_pCb->onSubstVarExpand(ctx,expr,&ret,xbString(),opm))
	{
		if (opm == xbOp_Optional)
			return xbString();
		ctx.throwError("Undefined variable");
	}
	return ret;
}

xbString xbSubstFmt(LPCSTR pFmt,const xbString &str,LPCSTR pDfltFmt,sIxbSubst *pCb)
{
	if (pCb)
		return xbSubstVar(pCb,pFmt,str,NULL,NULL,pDfltFmt);

	xbSubstForException cb(NULL);
	return xbSubstVar(&cb,pFmt,str,NULL,NULL,pDfltFmt);
}

xbString xbSubstNoFmt(LPCSTR pFmt,const xbString &str,LPCSTR pDfltFmt)
{
	xbSubstExecSt ctx(NULL,pFmt,NULL,NULL,str,pDfltFmt);
	return ctx.convert(str,NULL);
}


bool xbSubstForException::onSubstVarExpand(xbSubstExecSt &ctx,const xbString &varName,xbString *pVarValue,const xbString &ctxOp,xbOpMode opm,bool bPointCall)
{
	if (!bPointCall && ctxOp.IsValid() && !ctx.ctxValid(ctxOp))
		return false;
	if (varName.CompareNoCase("cppdebug") == 0)
	{
		if (pVarValue)
			*pVarValue = g_xbCfgEngine.GetSettingBoolToNum("CppDebug")!=-1?"true":"false";
		return true;
	}
	if (varName.CompareNoCase("localcharset") == 0)
	{
		if (pVarValue)
			*pVarValue = g_xbCfgEngine.GetCharsetFromCP(CP_ACP);
		return true;
	}
	if (!m_pE)
		return false;
	return m_pE->onSubstVarExpand(varName,pVarValue,opm);
}

bool xbSubstForException::onSubstUrlExpand(xbSubstExecSt &ctx,const xbString &url,const xbString &urlLabel,const xbString &urlHint,xbString *pRet)
{
	if (ctx.m_bKeepFmt)
		return false;

	if (xbStricmp(ctx.m_pFmt,"html") == 0)
	{
		ctx.m_bEscape = false;
		if (::xbGetProtocol(url).CompareNoCase("http") == 0)
		{
			*pRet = "<a href=\""+ctx.subst("url",url)+"\"";
			if (urlHint.IsValid())
				*pRet += " title=\""+ctx.subst(NULL,urlHint,"text")+"\"";
			if (urlLabel.IsValid())
				*pRet += " class=\"tokenUrlClick\">" + ctx.subst(NULL,urlLabel,"text") + "</a>";
			else
				*pRet += ">" + ctx.subst(NULL,url,"text") + "</a>";
			return true;
		}
		if (urlLabel.IsValid())
			*pRet = "<I><font color=blue>" + ctx.subst(NULL,urlLabel,"text") + "</font></I>";
		else
			*pRet = "<I><font color=blue>" + ctx.subst(NULL,url,"text") + "</font></I>";
		return true;
	}
	if (xbStricmp(ctx.m_pFmt,"xhtl") == 0)
	{
		ctx.m_bEscape = false;
		*pRet = "<tlink onclick=\"tokenUrlClick '"+ctx.subst("script",url)+"'\" style=\"tokenUrlClick\"";
		if (urlHint.IsValid())
			*pRet += " hint=\""+ctx.subst("script",urlHint,"text")+"\"";
		if (urlLabel.IsValid())
			*pRet += ">" + ctx.subst(NULL,urlLabel,"text") + "</tlink>";
		else
			*pRet += ">" + ctx.subst(NULL,url,"text") + "</tlink>";
		return true;
	}

	return true;
}
#pragma warning(pop)
