
//OpenSCADA file: tvariant.cpp
/***************************************************************************
 *   Copyright (C) 2010-2026 by Roman Savochenko, <roman@oscada.org>       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; version 2 of the License.               *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <algorithm>

#include "tsys.h"
#include "tvariant.h"

#if HAVE_PCRE_2
# define PCRE2_CODE_UNIT_WIDTH 0
# include <pcre2.h>
#else
# include <pcre.h>
#endif

using namespace OSCADA;

//*************************************************
//* TVariant                                      *
//*************************************************
TVariant::TVariant( ) : mType(Null), mModify(false), mFixedTp(false)
{
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( bool ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setB(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( char ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setB(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( int ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setI(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( int64_t ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setI(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( double ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setR(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( const string &ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setS(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( const char *ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setS(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( AutoHD<TVarObj> ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setO(ivl);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( TVarObj *ivl ) : mType(Null), mModify(false), mFixedTp(false)
{
    setO(AutoHD<TVarObj>(ivl));
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::TVariant( const TVariant &var ) : mType(Null), mModify(false), mFixedTp(false)
{
    operator=(var);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVariant::~TVariant( )
{
    setType(Null);
    //if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), -1);
}

void TVariant::setType( Type tp, bool fix, bool stdStringOmit )
{
    mFixedTp = fix;
    if(tp == type()) return;

    //Free
    switch(mType) {
	case String:
	    if(mStdString) delete val.s;
	    else if(mSize >= sizeof(val.sMini))	free(val.sPtr);
	    mSize = 0;
	    break;
	case Object:
	    if(val.o) delete val.o;
	    val.o = NULL;
	    break;
	default: break;
    }

    //Create
    mType = tp;
    switch(mType) {
	/*case Boolean:	setB(EVAL_BOOL);	break;
	case Integer:	setI(EVAL_INT);		break;
	case Real:	setR(EVAL_REAL);	break;*/
	case String:	mSize = 0; val.sMini[mSize] = 0; mStdString = false; mStdStringOmit = stdStringOmit; /*setS(EVAL_STR);*/	break;
	case Object:	val.o = new AutoHD<TVarObj>();	break;
	default: break;
    }
}

bool TVariant::operator==( const TVariant &vr ) const
{
    if(vr.type() == type())
	switch(type()) {
	    case Boolean:	return (vr.getB()==getB());
	    case Integer:	return (vr.getI()==getI());
	    case Real:		return (vr.getR()==getR());
	    case String:	return (vr.getS()==getS());
	    case Object:	return (vr.getO()==getO());
	    default: break;
	}

    return false;
}

bool TVariant::operator!=( const TVariant &vr ) const	{ return !operator==(vr); }

TVariant &TVariant::operator=( const TVariant &vr )
{
    switch(vr.type()) {
	case Null:
	    if(!mFixedTp) setType(Null);
	    else setS("");
	    break;
	case Boolean:	setB(vr.getB());	break;
	case Integer:	setI(vr.getI());	break;
	case Real:	setR(vr.getR());	break;
	case String:	setS(vr.getS()); mStdStringOmit = vr.mStdStringOmit;	break;
	case Object:	setO(vr.getO());	break;
    }

    return *this;
}

bool TVariant::isEVal( ) const
{
    switch(type()) {
	case String:	return (getS()==EVAL_STR);
	case Integer:	return (getI()==EVAL_INT);
	case Real:	return (getR()==EVAL_REAL);
	case Boolean:	return (getB()==EVAL_BOOL);;
	case Object:	return (getO().at().objName() == "EVAL");
	default: break;
    }
    return true;
}

char TVariant::getB( ) const
{
    switch(type()) {
	case Null:	return false;
	case String:	{ string tvl = getS(); return (tvl==EVAL_STR) ? EVAL_BOOL : (bool)s2i(tvl); }
	case Integer:	{ int64_t tvl = getI(); return (tvl==EVAL_INT) ? EVAL_BOOL : (bool)tvl; }
	case Real:	{ double tvl = getR(); return (tvl==EVAL_REAL) ? EVAL_BOOL : (bool)tvl; }
	case Object:	{ AutoHD<TVarObj> tvl = getO(); return (tvl.at().objName() == "EVAL") ? EVAL_BOOL : true; }
	case Boolean:	return val.b;
	default: break;
    }
    return EVAL_BOOL;
}

int64_t TVariant::getI( ) const
{
    switch(type()) {
	case String:	{ string tvl = getS(); return (tvl==EVAL_STR) ? EVAL_INT : s2ll(tvl); }
	case Real:	{ double tvl = getR(); return (tvl==EVAL_REAL) ? EVAL_INT : (int64_t)tvl; }
	case Boolean:	{ char tvl = getB();   return (tvl==EVAL_BOOL) ? EVAL_INT : tvl; }
	case Object:	{ AutoHD<TVarObj> tvl = getO(); return (tvl.at().objName() == "EVAL") ? EVAL_INT : 1; }
	case Integer:	return val.i;
	default: break;
    }
    return EVAL_INT;
}

double TVariant::getR( ) const
{
    switch(type()) {
	case String:	{ string tvl = getS(); return (tvl==EVAL_STR) ? EVAL_REAL : s2r(tvl); }
	case Integer:	{ int64_t tvl = getI();return (tvl==EVAL_INT) ? EVAL_REAL : tvl; }
	case Boolean:	{ char tvl = getB();   return (tvl==EVAL_BOOL) ? EVAL_REAL : tvl; }
	case Object:	{ AutoHD<TVarObj> tvl = getO(); return (tvl.at().objName() == "EVAL") ? EVAL_REAL : 1; }
	case Real:	return val.r;
	default: break;
    }
    return EVAL_REAL;
}

string TVariant::getS( ) const
{
    switch(type()) {
	case Integer:	{ int64_t tvl = getI();return (tvl==EVAL_INT) ? EVAL_STR : ll2s(tvl); }
	case Real:	{ double tvl = getR(); return (tvl==EVAL_REAL) ? EVAL_STR : r2s(tvl); }
	case Boolean:	{ char tvl = getB();   return (tvl==EVAL_BOOL) ? EVAL_STR : i2s(tvl); }
	case Object:	{ AutoHD<TVarObj> tvl = getO(); return (tvl.at().objName() == "EVAL") ? EVAL_STR : tvl.at().getStr(); }
	case String:
	    if(mStdString)	return *val.s;
	    if(mSize < sizeof(val.sMini)) return string(val.sMini, mSize);
	    return string(val.sPtr, mSize);
	default: break;
    }
    return EVAL_STR;
}

AutoHD<TVarObj>	TVariant::getO( ) const
{
    if(type() != Object)	return new TEValObj();
    if(val.o->freeStat())	*val.o = AutoHD<TVarObj>(new TEValObj());
    return *val.o;
}

void TVariant::setB( char ivl )
{
    if(type() != Boolean && !mFixedTp) setType(Boolean);
    switch(type()) {
	case String:	setS((ivl==EVAL_BOOL) ? EVAL_STR : i2s(ivl));	break;
	case Integer:	setI((ivl==EVAL_BOOL) ? EVAL_INT : ivl);	break;
	case Real:	setR((ivl==EVAL_BOOL) ? EVAL_REAL : ivl);	break;
	case Boolean:	val.b = ivl;					break;
	default: break;
    }
}

void TVariant::setI( int64_t ivl )
{
    if(type() != Integer && !mFixedTp) setType(Integer);
    switch(type()) {
	case String:	setS((ivl==EVAL_INT) ? EVAL_STR : ll2s(ivl));	break;
	case Integer:	val.i = ivl;					break;
	case Real:	setR((ivl==EVAL_INT) ? EVAL_REAL : ivl);	break;
	case Boolean:	setB((ivl==EVAL_INT) ? EVAL_BOOL : (bool)ivl);	break;
	default: break;
    }
}

void TVariant::setR( double ivl )
{
    if(type() != Real && !mFixedTp) setType(Real);
    switch(type()) {
	case String:	setS((ivl==EVAL_REAL) ? EVAL_STR : r2s(ivl));		break;
	case Integer:	setI((ivl==EVAL_REAL) ? EVAL_INT : (int64_t)ivl);	break;
	case Real:	val.r = ivl;						break;
	case Boolean:	setB((ivl==EVAL_REAL) ? EVAL_BOOL : (bool)ivl);		break;
	default: break;
    }
}

void TVariant::setS( const string &ivl )
{
    if(type() != String && !mFixedTp) setType(String);
    switch(type()) {
	case String:
	    if(ivl.size() < sizeof(val.sMini)) {	//Minimum fixed area
		if(mStdString) { delete val.s; mStdString = false; }
		else if(mSize >= sizeof(val.sMini)) free(val.sPtr);
		memcpy(val.sMini, ivl.data(), ivl.size());
		val.sMini[ivl.size()] = 0;
		mSize = ivl.size();
	    }
	    //!!!! Direct memory allocation mostly used now for constant strings like to the TCfg keyw to prevent they "const char *" pointer changing.
	    //     Maybe further here has a sense to use STL string also but check it to equal for the assign, then the pointer changing omit.
	    else if(mStdStringOmit) {		//For middle blocks up to prmStrBuf_SZ
		if(ivl.size() > 30000000)	throw TError("TVariant", _("Very large string for non STL string (> 30 MB)!"));
		if(mStdString) { delete val.s; mStdString = false; }
		if(mSize < sizeof(val.sMini))	val.sPtr = (char*)malloc(ivl.size()+1);
		else if(ivl.size() != mSize) {
		    char *sPtrTmp = val.sPtr;
		    val.sPtr = (char*)realloc(val.sPtr, ivl.size()+1);
		    if(sPtrTmp && !val.sPtr) free(sPtrTmp);
		}
		if(!val.sPtr) {
		    mSize = 0;
		    val.sMini[mSize] = 0;
		    throw TError("TVariant", _("Error allocating memory for large string (%d)!"), ivl.size());
		}
		memcpy(val.sPtr, ivl.data(), ivl.size());
		val.sPtr[ivl.size()] = 0;
		mSize = ivl.size();
	    }
	    else {					//Standard string for too big strings
		if(!mStdString) {
		    if(mSize >= sizeof(val.sMini)) free(val.sPtr);
		    val.s = new string;
		    mSize = 0; mStdString = true;
		}
		*val.s = ivl;
	    }
	    break;
	case Integer:	setI((ivl==EVAL_STR) ? EVAL_INT : s2ll(ivl));	break;
	case Real:	setR((ivl==EVAL_STR) ? EVAL_REAL : s2r(ivl));	break;
	case Boolean:	setB((ivl==EVAL_STR) ? EVAL_BOOL : (bool)s2i(ivl));	break;
	case Object:	setO((ivl==EVAL_STR) ? AutoHD<TVarObj>(new TEValObj()) : TVarObj::parseStr(ivl,getO())); break;
	default: break;
    }
}

void TVariant::setO( AutoHD<TVarObj> ivl )
{
    if(type() != Object && !mFixedTp) setType(Object);
    *val.o = ivl;
}

void TVariant::setO( TVarObj *ival )
{
    setO(AutoHD<TVarObj>(ival));
}

//***********************************************************
//* TVarObj                                                 *
//*   Variable object, by default included properties only  *
//***********************************************************
TVarObj::TVarObj( ) : mUseCnt(0), dataM(true)
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), 1);
}

TVarObj::~TVarObj( )
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(objName(), -1);
}

void TVarObj::AHDConnect( )
{
    dataM.lock();
    ++mUseCnt;
    dataM.unlock();
}

bool TVarObj::AHDDisConnect( )
{
    dataM.lock();
    bool dblDscNct = (mUseCnt == 0);
    if(mUseCnt) mUseCnt--;
    bool toFree = (mUseCnt == 0 && !dblDscNct);
    dataM.unlock();

    if(dblDscNct) mess_err("TVarObj", _("Attempt to double disconnect!"));

    return toFree;
}

void TVarObj::propList( vector<string> &ls )
{
    ls.clear();
    dataM.lock();
    for(map<string,TVariant>::iterator ip = mProps.begin(); ip != mProps.end(); ip++)
	ls.push_back(ip->first);
    dataM.unlock();
}

TVariant TVarObj::propGet( const string &id )
{
    dataM.lock();
    TVariant rez;
    map<string,TVariant>::iterator vit = mProps.find(id);
    if(vit != mProps.end()) rez = vit->second;
    dataM.unlock();

    return rez;
}

TVariant TVarObj::propGet( const string &ids, char sep )
{
    TVariant obj = this;
    string tid;
    if(sep) for(int off = 0; (tid=TSYS::strSepParse(ids,0,sep,&off)).size() && obj.type() == TVariant::Object; )
	obj = obj.getO().at().propGet(tid);
    else for(int off = 0; (tid=TSYS::pathLev(ids,0,true,&off)).size() && obj.type() == TVariant::Object; )
	obj = obj.getO().at().propGet(tid);

    return tid.size() ? TVariant((char)EVAL_BOOL) : obj;
}

void TVarObj::propSet( const string &id, TVariant val )
{
    dataM.lock();
    mProps[id] = val;
    dataM.unlock();
}

void TVarObj::propSet( const string &ids, char sep, TVariant val )
{
    TVariant obj = this;
    string tid;
    int off = 0;
    if(sep) while(obj.type() == TVariant::Object && (tid=TSYS::strSepParse(ids,0,sep,&off)).size() && off < (int)ids.size())
	obj = obj.getO().at().propGet(tid);
    else while(obj.type() == TVariant::Object && (tid=TSYS::pathLev(ids,0,true,&off)).size() && off < (int)ids.size())
	obj = obj.getO().at().propGet(tid);
    if(tid.size() && off >= (int)ids.size())	obj.getO().at().propSet(tid, val);
}

void TVarObj::propClear( const string &ids )
{
    dataM.lock();
    if(ids.size()) mProps.erase(ids);
    else mProps.clear();
    dataM.unlock();
}

string TVarObj::getStr( const string &tp )
{
    return (tp == "xml" || (objName() != "object" && objName() != "array")) ? getStrXML() : getStrJSON();
}

string TVarObj::getStrXML( const string &oid )
{
    string nd("<TVarObj");
    if(!oid.empty()) nd += " p='" + oid + "'";
    nd += ">\n";

    dataM.lock();
    for(map<string,TVariant>::iterator ip = mProps.begin(); ip != mProps.end(); ip++)
	switch(ip->second.type()) {
	    case TVariant::String: nd += "<str p='"+ip->first+"'>"+TSYS::strEncode(ip->second.getS(),TSYS::Html)+"</str>\n";	break;
	    case TVariant::Integer:nd += "<int p='"+ip->first+"'>"+TSYS::strEncode(ip->second.getS(),TSYS::Html)+"</int>\n";	break;
	    case TVariant::Real:   nd += "<real p='"+ip->first+"'>"+TSYS::strEncode(ip->second.getS(),TSYS::Html)+"</real>\n";	break;
	    case TVariant::Boolean:nd += "<bool p='"+ip->first+"'>"+TSYS::strEncode(ip->second.getS(),TSYS::Html)+"</bool>\n";	break;
	    case TVariant::Object: nd += ip->second.getO().at().getStrXML(ip->first); break;
	    default: break;
	}
    dataM.unlock();
    nd += "</TVarObj>\n";

    return nd;
}

string TVarObj::getStrJSON( TVariant *it, unsigned lev )
{
    if(it) {
	if(it->isEVal()) return "null";
	switch(it->type()) {
	    case TVariant::String: return "\""+TSYS::strEncode(it->getS(),TSYS::ShieldSymb,"\\\"\b\f\n\r\t")+"\"";
	    case TVariant::Boolean:return it->getB() ? "true" : "false";
	    case TVariant::Object: return it->getO().at().getStrJSON(NULL, lev+1);
	    default: return it->getS();
	}
	return "";
    }

    string nd("{");

    dataM.lock();
    bool notFirst = false;
    for(map<string,TVariant>::iterator ip = mProps.begin(); ip != mProps.end(); ++ip, notFirst = true)
	nd = nd + (notFirst?",":"")+"\n"+string(2*(lev+1),' ')+
		"\""+TSYS::strEncode(ip->first,TSYS::ShieldSymb,"\\\"\b\f\n\r\t")+"\": "+getStrJSON(&(ip->second), lev);
    dataM.unlock();
    nd = nd + (notFirst?"\n":"") + string(2*lev,' ')+"}";

    return nd;
}

AutoHD<TVarObj> TVarObj::parseStr( const string &str, AutoHD<TVarObj> prev )
{
    AutoHD<TVarObj> obj = TVarObj::parseStrJSON(str, prev);
    if(obj == prev) obj = TVarObj::parseStrXML(str, NULL, prev);
    return obj;
}

AutoHD<TVarObj> TVarObj::parseStrXML( const string &str, XMLNode *nd, AutoHD<TVarObj> prev )
{
    XMLNode oTree;
    if(!nd) {
	try { oTree.load(str, 0, Mess->charset()); }
	catch(TError &err) { return prev; }
	nd = &oTree;
    }

    //Different objects process
    if(nd->name() == "TVarObj") {
	TVarObj *rez = new TVarObj;
	for(unsigned iCh = 0; iCh < nd->childSize(); iCh++) {
	    XMLNode *cNd = nd->childGet(iCh);
	    if(cNd->name() == "str")		rez->mProps[cNd->attr("p")] = cNd->text();
	    else if(cNd->name() == "int")	rez->mProps[cNd->attr("p")] = (int64_t)s2ll(cNd->text());
	    else if(cNd->name() == "real")	rez->mProps[cNd->attr("p")] = s2r(cNd->text());
	    else if(cNd->name() == "bool")	rez->mProps[cNd->attr("p")] = (char)s2i(cNd->text());
	    else if(cNd->name() == "TVarObj")	rez->mProps[cNd->attr("p")] = TVarObj::parseStrXML("", cNd);
	    else if(cNd->name() == "TEValObj")	rez->mProps[cNd->attr("p")] = TEValObj::parseStrXML(cNd);
	    else if(cNd->name() == "TArrayObj")	rez->mProps[cNd->attr("p")] = TArrayObj::parseStrXML(cNd);
	    else if(TSYS::strParse(cNd->name(),0,":") == "XMLNodeObj")
		rez->mProps[cNd->attr("p")] = XMLNodeObj::parseStrXML(cNd);
	}
	return rez;
    }
    else if(nd->name() == "TEValObj")	return TEValObj::parseStrXML(nd);
    else if(nd->name() == "TArrayObj")	return TArrayObj::parseStrXML(nd);
    else if(TSYS::strParse(nd->name(),0,":") == "XMLNodeObj")	return XMLNodeObj::parseStrXML(nd);

    return prev;
}

AutoHD<TVarObj> TVarObj::parseStrJSON( const string &str, AutoHD<TVarObj> prev )
{
    TVarObj *rootO = NULL;
    vector<TVarObj*> objs;
    unsigned pos = 0, posB = 0;
    string propNm;
    TVariant propVl;

    try {
	for( ; pos < str.size(); ++pos)
	    if(isspace(str[pos])) ;
	    else if(rootO && objs.empty()) throw TError("TVarObj", _("No opened dictionary - spare closing. Pos: %u"), pos);
	    //Opening
	    else if(str[pos] == '{' || str[pos] == '[') {
		TVarObj *tObj = (str[pos] == '{') ? new TVarObj : new TArrayObj;
		if(!rootO) rootO = tObj;
		else if(objs.size()) {
		    if(objs.back()->objName() == "array") ((TArrayObj*)objs.back())->arSet(-1, tObj);
		    else objs.back()->mProps[propNm] = tObj;
		    propNm = "";
		}
		objs.push_back(tObj);
	    }
	    //Closing
	    else if(str[pos] == '}' || str[pos] == ']') {
		if(str[pos] == '}' && objs.back()->objName() != "object") throw TError("TVarObj", _("Closing not dictionary. Pos: %u"), pos);
		if(str[pos] == ']' && objs.back()->objName() != "array") throw TError("TVarObj", _("Closing not list. Pos: %u"), pos);
		objs.pop_back();
	    }
	    else if(!rootO) throw TError("TVarObj", _("No opening global container. Pos: %u"), pos);
	    else if(str[pos] == ',') ;
	    //Property name or string value
	    else if(str[pos] == '"') {
		// ... name
		for(posB = ++pos; pos < str.size() && (str[pos] != '\"' || str[pos-1] == '\\'); ++pos) ;
		if(pos >= str.size()) throw TError("TVarObj", _("Unexpected end."));
		propVl = TSYS::strDecode(str.substr(posB,pos-posB), TSYS::ShieldSymb);

		if(propNm.empty() && objs.back()->objName() != "array") {
		    if(propVl.getS().empty()) throw TError("TVarObj", _("Empty attribute name. Pos: %u"), posB);
		    propNm = propVl.getS();

		    for(++pos; pos < str.size() && isspace(str[pos]); ++pos) ;
		    if(pos >= str.size()) throw TError("TVarObj", _("Unexpected end."));
		    if(str[pos] != ':') throw TError("TVarObj", _("Wrong delimiter ':' in a property '%s'. Pos: %u"), propNm.c_str(), pos);
		}
		// ... string value
		else {
		    if(objs.back()->objName() == "array") ((TArrayObj*)objs.back())->arSet(-1, propVl);
		    else objs.back()->mProps[propNm] = propVl;
		    propNm = "";
		}
	    }
	    //Not quoted values
	    else {
		for(posB = pos; pos < str.size() && !isspace(str[pos]) && str[pos] != ',' && str[pos] != '}' && str[pos] != ']'; ++pos) ;
		if(pos >= str.size()) throw TError("TVarObj", _("Unexpected end."));
		propVl = str.substr(posB, pos-posB); --pos;
		if(TRegExp("^[+-]?\\d+$").test(propVl.getS()))
		    propVl = s2i(propVl.getS());
		else if(TRegExp("^[+-]?\\d+\\.?\\d*[eE]?[+-]?\\d*$").test(propVl.getS()))
		    propVl = s2r(propVl.getS());
		else if(propVl.getS() == "true")  propVl = true;
		else if(propVl.getS() == "false") propVl = false;
		else if(propVl.getS() == "null")  propVl = EVAL_STR;

		if(objs.back()->objName() == "array") ((TArrayObj*)objs.back())->arSet(-1, propVl);
		else objs.back()->mProps[propNm] = propVl;
		propNm = "";
	    }

	if(objs.size()) throw TError("TVarObj", _("Not closed %d."), objs.size());
    }
    catch(TError &err) {
	if(rootO) mess_warning("TVarObj", _("Error JSON parsing. %s"), err.mess.c_str());
    }

    return rootO ? AutoHD<TVarObj>(rootO) : prev;
}

TVariant TVarObj::funcCall( const string &id, vector<TVariant> &prms )
{
    // bool isEVal() - return "false" for EVAL detect
    if(id == "isEVal")	return false;

    throw TError(TSYS::strMess("Obj%s",objName().c_str()).c_str(), _("Error the function '%s' or missing its parameters."), id.c_str());
}

//*****************************************************************
//* TEValObj                                                      *
//*   Special EVal object — Scalar bool, int, real, string analog *
//*****************************************************************
TEValObj::TEValObj( )
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), 1);
}

TEValObj::~TEValObj( )
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), -1);
}

TVariant TEValObj::funcCall( const string &id, vector<TVariant> &prms )
{
    // bool isEVal() - return "true" for EVAL detect
    if(id == "isEVal")	return true;

    return TVarObj::funcCall(id, prms);
}

string TEValObj::getStrXML( const string &oid )
{
    string nd("<TEValObj");
    if(!oid.empty()) nd += " p='" + oid + "'";
    nd += " />\n";
    return nd;
}

AutoHD<TVarObj> TEValObj::parseStrXML( XMLNode *nd )	{ return new TEValObj; }

//***********************************************************
//* TArrayObj                                                *
//*   Array object included indexed properties               *
//***********************************************************
TArrayObj::TArrayObj( )
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), 1);
}

TArrayObj::~TArrayObj( )
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), -1);
}

TVariant TArrayObj::propGet( const string &id )
{
    if(id == "length")			return (int)mEls.size();
    if(id.size() && isdigit(id[0]))	return arGet(s2i(id));
    return TVarObj::propGet(id);
}

void TArrayObj::propSet( const string &id, TVariant val )
{
    if(id.size() && isdigit(id[0]))	arSet(s2i(id), val);
    else TVarObj::propSet(id,val);
}

string TArrayObj::getStrXML( const string &oid )
{
    string nd("<TArrayObj");
    if(!oid.empty()) nd = nd + " p='" + oid + "'";
    nd = nd + ">\n";

    dataM.lock();
    //Array items process
    for(unsigned ip = 0; ip < mEls.size(); ip++)
	switch(mEls[ip].type()) {
	    case TVariant::String: nd += "<str>"+TSYS::strEncode(mEls[ip].getS(),TSYS::Html)+"</str>\n";	break;
	    case TVariant::Integer:nd += "<int>"+TSYS::strEncode(mEls[ip].getS(),TSYS::Html)+"</int>\n";	break;
	    case TVariant::Real:   nd += "<real>"+TSYS::strEncode(mEls[ip].getS(),TSYS::Html)+"</real>\n";	break;
	    case TVariant::Boolean:nd += "<bool>"+TSYS::strEncode(mEls[ip].getS(),TSYS::Html)+"</bool>\n";	break;
	    case TVariant::Object: nd += mEls[ip].getO().at().getStrXML(); break;
	    default: break;
	}
    //Object's properties process
    for(map<string,TVariant>::iterator ip = mProps.begin(); ip != mProps.end(); ip++)
	switch(ip->second.type()) {
	    case TVariant::String: nd += "<str p='"+ip->first+"'>"+TSYS::strEncode(ip->second.getS(),TSYS::Html)+"</str>\n"; break;
	    case TVariant::Integer:nd += "<int p='"+ip->first+"'>"+ip->second.getS()+"</int>\n";	break;
	    case TVariant::Real:   nd += "<real p='"+ip->first+"'>"+ip->second.getS()+"</real>\n";	break;
	    case TVariant::Boolean:nd += "<bool p='"+ip->first+"'>"+ip->second.getS()+"</bool>\n";	break;
	    case TVariant::Object: nd += ip->second.getO().at().getStrXML(ip->first);			break;
	    default: break;
	}
    dataM.unlock();

    nd += "</TArrayObj>\n";

    return nd;
}

AutoHD<TVarObj> TArrayObj::parseStrXML( XMLNode *nd )
{
    TArrayObj *rez = new TArrayObj;
    for(unsigned iCh = 0; iCh < nd->childSize(); iCh++) {
	XMLNode *cNd = nd->childGet(iCh);
	string p = cNd->attr("p");
	TVariant vl;
	if(cNd->name() == "str")	vl = cNd->text();
	else if(cNd->name() == "int")	vl = (int64_t)s2ll(cNd->text());
	else if(cNd->name() == "real")	vl = s2r(cNd->text());
	else if(cNd->name() == "bool")	vl = (char)s2i(cNd->text());
	else vl = TVarObj::parseStrXML("", cNd);
	if(!vl.isNull()) {
	    if(p.size()) rez->mProps[p] = vl;
	    else rez->mEls.push_back(vl);
	}
    }
    return rez;
}

string TArrayObj::getStrJSON( TVariant *it, unsigned lev )
{
    if(it) return TVarObj::getStrJSON(it, lev);

    string nd("[");

    dataM.lock();
    bool notFirst = false;
    for(vector<TVariant>::iterator ip = mEls.begin(); ip != mEls.end(); ++ip, notFirst = true)
	nd = nd + (notFirst?",":"") + "\n" + string(2*(lev+1),' ')+getStrJSON(&(*ip), lev);
    dataM.unlock();
    nd = nd + (notFirst?"\n":"") + string(2*lev,' ')+"]";

    return nd;
}

TVariant TArrayObj::funcCall( const string &id, vector<TVariant> &prms )
{
    // string join( string sep = "," ) - join items to string
    //  sep - items separator
    if(id == "join" || id == "toString" || id == "valueOf") {
	string rez, sep = prms.size() ? prms[0].getS() : ",";
	dataM.lock();
	for(unsigned iE = 0; iE < mEls.size(); iE++)
	    rez += (iE?sep:"")+mEls[iE].getS();
	dataM.unlock();
	return rez;
    }
    // TArrayObj concat( TArrayObj arr ) - concatenate array
    //  arr - source array
    if(id == "concat" && prms.size()) {
	AutoHD<TArrayObj> sArr;
	if(prms[0].type() != TVariant::Object || (sArr=prms[0].getO()).freeStat())	return this;
	dataM.lock();
	for(unsigned iP = 0; iP < sArr.at().arSize(); iP++) mEls.push_back(sArr.at().arGet(iP));
	dataM.unlock();
	return this;
    }
    // int push( ElTp var, ... ) - push variables to array
    //  var - variable
    if(id == "push" && prms.size()) {
	dataM.lock();
	for(unsigned iP = 0; iP < prms.size(); iP++) mEls.push_back(prms[iP]);
	dataM.unlock();
	return (int)mEls.size();
    }
    // ElTp pop( ) - pop variable from array
    if(id == "pop") {
	if(mEls.empty()) return EVAL_REAL;
	dataM.lock();
	TVariant val = mEls.back();
	mEls.pop_back();
	dataM.unlock();
	return val;
    }
    // Array reverse( ) - reverse array's items order
    if(id == "reverse") {
	dataM.lock();
	reverse(mEls.begin(),mEls.end());
	dataM.unlock();
	return this;
    }
    // ElTp shift( ) - shift array's items upward
    if(id == "shift") {
	if(mEls.empty()) return EVAL_REAL;
	dataM.lock();
	TVariant val = mEls.front();
	mEls.erase(mEls.begin());
	dataM.unlock();
	return val;
    }
    // int unshift( ElTp var, ... ) - shift items to array upward
    //  var - variable
    if(id == "unshift" && prms.size()) {
	dataM.lock();
	for(unsigned iP = 0; iP < prms.size(); iP++) mEls.insert(mEls.begin()+iP, prms[iP]);
	dataM.unlock();
	return (int)mEls.size();
    }
    // Array slice( int beg, int end = {end} ) - get array part from positon <beg> to <end> (exclude)
    //  beg - begin position
    //  end - end position
    if(id == "slice" && prms.size()) {
	dataM.lock();
	int beg = prms[0].getI();
	if(beg < 0) beg = mEls.size()+beg;
	beg = vmax(beg,0);
	int end = mEls.size();
	if(prms.size() >= 2) end = prms[1].getI();
	if(end < 0) end = mEls.size()+end;
	end = vmin(end, (int)mEls.size());
	TArrayObj *rez = new TArrayObj();
	for(int iP = beg; iP < end; iP++)
	    rez->arSet(iP-beg, mEls[iP]);
	dataM.unlock();
	return rez;
    }
    // Array splice( int beg, int remN = {end}[, ElTp val1[, ElTp val2, ...]] ) - insert, remove or replace array's items
    //  beg - start position
    //  remN - removed items number
    //  val1, val2, ... - values for insert
    if(id == "splice" && prms.size() >= 1) {
	dataM.lock();
	int beg = vmax(0, prms[0].getI());
	int cnt = (prms.size()>1) ? prms[1].getI() : mEls.size();
	//  Delete elements
	TArrayObj *rez = new TArrayObj();
	for(int iC = 0; iC < cnt && beg < (int)mEls.size(); iC++) {
	    rez->arSet(iC, mEls[beg]);
	    mEls.erase(mEls.begin()+beg);
	}
	//  Insert elements
	for(unsigned iC = 2; iC < prms.size() && beg <= (int)mEls.size(); iC++)
	    mEls.insert(mEls.begin()+beg+iC-2,prms[iC]);
	dataM.unlock();
	return rez;
    }
    // int indexOf( ElTp var, int start = 0 ) - returns the array index of the required variable <var> in the original
    //       row from the position <start>
    //  var - requested variable
    //  start - start position for search
    if(id == "indexOf" && prms.size()) {
	dataM.lock();
	size_t sp = 0;
	if(prms.size() > 1) sp = vmax(0, vmin(mEls.size()-1,(unsigned)prms[1].getI()));
	string sVl = prms[0].getS();
	unsigned iE = sp;
	while(iE < mEls.size() && mEls[iE].getS() != sVl) iE++;
	int rez = (iE < mEls.size()) ? (int)iE : -1;
	dataM.unlock();
	return rez;
    }
    // int lastIndexOf( ElTp var, int start ) - returns the array index of the required variable <var> in the original
    //       row from the position <start> when searching from the end
    //  var - requested variable
    //  start - start position for search from the end
    if(id == "lastIndexOf" && prms.size()) {
	dataM.lock();
	string sVl = prms[0].getS();
	int iE = mEls.size();
	while(iE >= 0 && mEls[iE].getS() != sVl) iE--;
	int rez = (iE < (int)mEls.size()) ? iE : -1;
	dataM.unlock();
	return rez;
    }
    // double sum( int beg, int end = {end} ) - sum of the array values part from the position <beg> to <end>, excluding
    //  beg - begin position
    //  end - end position
    if(id == "sum" && prms.size()) {
	dataM.lock();
	int beg = prms[0].getI();
	if(beg < 0) beg = mEls.size()+beg;
	beg = vmax(beg, 0);
	int end = mEls.size();
	if(prms.size() >= 2) end = prms[1].getI();
	if(end < 0) end = mEls.size()+end;
	end = vmin(end, (int)mEls.size());
	double rez = 0;
	for(int iP = beg; iP < end; iP++) rez += mEls[iP].getR();
	dataM.unlock();
	return rez;
    }
    // Array sort( ) - lexicographic items sorting
    if(id == "sort") {
	dataM.lock();
	sort(mEls.begin(), mEls.end(), compareLess);
	dataM.unlock();
	return this;
    }

    return TVarObj::funcCall(id, prms);
}

TVariant TArrayObj::arGet( int vid )
{
    TVariant rez;
    dataM.lock();
    if(vid >= 0 && vid < (int)mEls.size()) rez = mEls[vid];
    dataM.unlock();
    return rez;
}

void TArrayObj::arSet( int vid, TVariant val )
{
    dataM.lock();
    if(vid < 0) vid = mEls.size();
    while(vid >= (int)mEls.size()) mEls.push_back(TVariant());
    mEls[vid] = val;
    dataM.unlock();
}

bool TArrayObj::compareLess( const TVariant &v1, const TVariant &v2 )	{ return v1.getS() < v2.getS(); }

//***********************************************************
//* TRegExp                                                 *
//*   Regular expression object                             *
//***********************************************************
#define TRegExp_vSz 90

TRegExp::TRegExp( const string &rule, const string &flg, char mode ) :
    lastIndex(0), pattern(rule), global(false), ignoreCase(false), multiline(false), ungreedy(false), isSimplePat(false), UTF8(false),
    regex(NULL), md(MD_8),
#if HAVE_PCRE_2
    mDt(NULL)
#else
    capv(NULL)
#endif
{
    setPattern(rule, flg, mode);

    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), 1);
}

TRegExp::~TRegExp( )
{
#if HAVE_PCRE_2
    if(mDt)	pcre2_match_data_free_8((pcre2_match_data_8*)mDt);
    if(regex) {
	if(md == MD_8)		pcre2_code_free_8((pcre2_code_8*)regex);
# if HAVE_PCRE32
	else if(md == MD_32)	pcre2_code_free_32((pcre2_code_32*)regex);
# endif
# if HAVE_PCRE16
	else if(md == MD_16)	pcre2_code_free_16((pcre2_code_16*)regex);
# endif
#else
    if(capv)	delete [] capv;
    if(regex) {
	if(md == MD_8)		pcre_free((pcre*)regex);
# if HAVE_PCRE32
	else if(md == MD_32)	pcre32_free((pcre32*)regex);
# endif
# if HAVE_PCRE16
	else if(md == MD_16)	pcre16_free((pcre16*)regex);
# endif
#endif
    }

    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), -1);
}

void TRegExp::setPattern( const string &rule, const string &flg, char mode )
{
    //Global properties init
    global = (flg.find('g') != string::npos);
    ignoreCase = (flg.find('i') != string::npos);
    multiline = (flg.find('m') != string::npos);
    ungreedy = (flg.find('U') != string::npos);
    UTF8 = /*Mess->isUTF8() &&*/ (flg.find('u')!=string::npos);
    isSimplePat = false;
    pattern = rule;
    if(flg.find('p') != string::npos) {
	isSimplePat = !(rule.size() > 2 && rule[0] == '/' && rule[rule.size()-1] == '/');
	if(!isSimplePat) pattern = rule.substr(1,rule.size()-2);
	else if(pattern.empty()) pattern = "*";
    }

    //Check for free
#if HAVE_PCRE_2
    if(mDt)	{ pcre2_match_data_free_8((pcre2_match_data_8*)mDt); mDt = NULL; }
    if(regex) {
	if(md == MD_8)		pcre2_code_free_8((pcre2_code_8*)regex);
# if HAVE_PCRE32
	else if(md == MD_32)	pcre2_code_free_32((pcre2_code_32*)regex);
# endif
# if HAVE_PCRE16
	else if(md == MD_16)	pcre2_code_free_16((pcre2_code_16*)regex);
# endif
#else
    if(isSimplePat && capv)	{ delete [] capv; capv = NULL; }
    if(regex) {
	if(md == MD_8)		pcre_free((pcre*)regex);
# if HAVE_PCRE32
	else if(md == MD_32)	pcre32_free((pcre32*)regex);
# endif
# if HAVE_PCRE16
	else if(md == MD_16)	pcre16_free((pcre16*)regex);
# endif
#endif
	regex = NULL;
    }

    if(!isSimplePat) {
	if(mode == MD_SAVE) ;
#if HAVE_PCRE32
	else if(mode == MD_32 || (mode == MD_WCHAR && sizeof(wchar_t) == 4))	md = MD_32;
#endif
#if HAVE_PCRE16
	else if(mode == MD_16 || (mode == MD_WCHAR && sizeof(wchar_t) == 2))	md = MD_16;
#endif
	else md = MD_8;
    }

    //Alloc for regexp
    if(!isSimplePat && pattern.size()) {
#if HAVE_PCRE_2
	PCRE2_SIZE erroff;
	int cderr;
	int options = PCRE2_DOTALL|(ignoreCase?PCRE2_CASELESS:0)|(multiline?PCRE2_MULTILINE:0)|(ungreedy?PCRE2_UNGREEDY:0);

	if(md == MD_8) {
	    regex = pcre2_compile_8((PCRE2_SPTR8)pattern.c_str(), PCRE2_ZERO_TERMINATED,
					options|(UTF8?PCRE2_UTF|PCRE2_NO_UTF_CHECK:0), &cderr, &erroff, NULL);
	    if(regex && !mDt) mDt = pcre2_match_data_create_from_pattern_8((pcre2_code_8*)regex, NULL);
	}
# if HAVE_PCRE32
	else if(md == MD_32) {
	    regex = pcre2_compile_32((PCRE2_SPTR32)(pattern+string(4,0)).data(), PCRE2_ZERO_TERMINATED,
					options|(UTF8?PCRE2_UTF|PCRE2_NO_UTF_CHECK:0), &cderr, &erroff, NULL);
	    if(regex && !mDt) mDt = pcre2_match_data_create_from_pattern_32((pcre2_code_32*)regex, NULL);
	}
# endif
# if HAVE_PCRE16
	else if(md == MD_16) {
	    regex = pcre2_compile_16((PCRE2_SPTR16)(pattern+string(2,0)).data(), PCRE2_ZERO_TERMINATED,
					options|(UTF8?PCRE2_UTF|PCRE2_NO_UTF_CHECK:0), &cderr, &erroff, NULL);
	    if(regex && !mDt) mDt = pcre2_match_data_create_from_pattern_16((pcre2_code_16*)regex, NULL);
	}

	if(!regex) {
	    char tbuf[255];
	    pcre2_get_error_message_8(cderr, (PCRE2_UCHAR8*)tbuf, sizeof(tbuf));
	    err = tbuf;
	}
# endif
#else
	const char *terr;
	int erroff;
	int options = PCRE_DOTALL|(ignoreCase?PCRE_CASELESS:0)|(multiline?PCRE_MULTILINE:0)|(ungreedy?PCRE_UNGREEDY:0);

	if(md == MD_8) regex = pcre_compile(pattern.c_str(),
					options|(UTF8?PCRE_UTF8|PCRE_NO_UTF8_CHECK:0), &terr, &erroff, NULL);
# if HAVE_PCRE32
	else if(md == MD_32) regex = pcre32_compile((PCRE_SPTR32)(pattern+string(4,0)).data(),
					options|(UTF8?PCRE_UTF32|PCRE_NO_UTF32_CHECK:0), &terr, &erroff, NULL);
# endif
# if HAVE_PCRE16
	else if(md == MD_16) regex = pcre16_compile((PCRE_SPTR16)(pattern+string(2,0)).data(),
					options|(UTF8?PCRE_UTF16|PCRE_NO_UTF16_CHECK:0), &terr, &erroff, NULL);
# endif
	if(!regex) err = terr;
	else if(!capv) capv = new int[TRegExp_vSz];
#endif
    }
}

TArrayObj *TRegExp::match( const string &vl, bool all )
{
    TArrayObj *rez = new TArrayObj();
    if(!regex || md != MD_8) return rez;	//?!?! Not implemented still for 16/32 modes

#if HAVE_PCRE_2
    PCRE2_SIZE *capv = pcre2_get_ovector_pointer_8((pcre2_match_data_8*)mDt);
#endif

    if(all && global)
	for(int curPos = 0, iN = 0;
#if HAVE_PCRE_2
		pcre2_match_8((pcre2_code_8*)regex,(PCRE2_SPTR8)vl.data(),vl.size(),curPos,0,(pcre2_match_data_8*)mDt,NULL) > 0
#else
		pcre_exec((pcre*)regex,NULL,vl.data(),vl.size(),curPos,0,capv,TRegExp_vSz) > 0
#endif
		    && capv[1] > capv[0]; curPos = capv[1], iN++)
	    rez->arSet(iN, string(vl.data()+capv[0],capv[1]-capv[0]));
    else {
#if HAVE_PCRE_2
	int n = pcre2_match_8((pcre2_code_8*)regex,(PCRE2_SPTR8)vl.data(),vl.size(),(global?lastIndex:0),0,(pcre2_match_data_8*)mDt,NULL);
#else
	int n = pcre_exec((pcre*)regex, NULL, vl.data(), vl.size(), (global?lastIndex:0), 0, capv, TRegExp_vSz);
#endif
	for(int iN = 0; iN < n; iN++)
	    rez->arSet(iN, string(vl.data()+capv[iN*2],capv[iN*2+1]-capv[iN*2]));
	if(global) lastIndex = (n>0) ? capv[1] : 0;
	if(n > 0) { rez->propSet("index", (int)capv[0]); rez->propSet("input", vl); }
	else if(n < 0) { rez->propSet("err", i2s(n)); }
    }

    return rez;
}

string TRegExp::replace( const string &vl, const string &str )
{
    string rez = vl, repl;
    if(!regex || md != MD_8) return rez;	//?!?! Not implemented still for 16/32 modes

#if HAVE_PCRE_2
    PCRE2_SIZE *capv = pcre2_get_ovector_pointer_8((pcre2_match_data_8*)mDt);
#endif

    for(int curPos = 0, n; (!curPos || global) &&
#if HAVE_PCRE_2
	    (n=pcre2_match_8((pcre2_code_8*)regex,(PCRE2_SPTR8)rez.data(),rez.size(),curPos,0,(pcre2_match_data_8*)mDt,NULL)) > 0
#else
	    (n=pcre_exec((pcre*)regex,NULL,rez.data(),rez.size(),curPos,0,capv,TRegExp_vSz)) > 0
#endif
		&& capv[1] >= capv[0];
	curPos = capv[0]+repl.size())
    {
	repl = substExprRepl(str, rez, capv, n);
	rez.replace(capv[0], capv[1]-capv[0], repl);
    }
    return rez;
}

TArrayObj *TRegExp::split( const string &vl, int limit )
{
    TArrayObj *rez = new TArrayObj();
    if(!regex || md != MD_8) return rez;	//?!?! Not implemented still for 16/32 modes

#if HAVE_PCRE_2
    PCRE2_SIZE *capv = pcre2_get_ovector_pointer_8((pcre2_match_data_8*)mDt);
#endif

    int curPos = 0, iN = 0;
    for(int se = 0;
#if HAVE_PCRE_2
	    (se=pcre2_match_8((pcre2_code_8*)regex,(PCRE2_SPTR8)vl.data(),vl.size(),curPos,0,(pcre2_match_data_8*)mDt,NULL)) > 0
#else
	    (se=pcre_exec((pcre*)regex,NULL,vl.data(),vl.size(),curPos,0,capv,TRegExp_vSz)) > 0
#endif
		    && capv[1] > capv[0] && (!limit || iN < limit);
		curPos = capv[1])
    {
	rez->arSet(iN++, string(vl.data()+curPos,capv[0]-curPos));
	for(int iSe = 1; iSe < se && (!limit || iN < limit); iSe++)
	    rez->arSet(iN++, string(vl.data()+capv[iSe*2],capv[iSe*2+1]-capv[iSe*2]));
    }
    if(curPos <= (int)vl.size() && (!limit || iN < limit)) rez->arSet(iN++, string(vl.data()+curPos,vl.size()-curPos));
    return rez;
}

bool TRegExp::test( const string &vl )
{
    //Check by simple pattern
    if(isSimplePat) {
	bool multS = false;
	int vCnt = 0, pCnt = 0;
	int vBck = -1, pBck = -1;

	while(true) {
	    if(pCnt > (int)pattern.size()) return true;	//!!!! Including the end-string symbol (0)
	    if(vCnt > (int)vl.size()) break;		//!!!! Including the end-string symbol (0)
	    const char pCh = pattern[pCnt];
	    if(pCh == '?')	{ vCnt++; pCnt++; multS = false; continue; }
	    if(pCh == '*')	{ pCnt++; multS = true; vBck = -1; continue; }
	    if(pCh == '\\')	pCnt++;
	    if(pCh == vl[vCnt]) {
		if(multS && vBck < 0)	{ vBck = vCnt+1; pBck = pCnt; }
		vCnt++; pCnt++;
	    }
	    else {
		if(multS) {
		    if(vBck >= 0) { vCnt = vBck; pCnt = pBck; vBck = -1; }
		    else vCnt++;
		}
		else break;
	    }
	}
	return false;
    }
    //Check by regular expression
    if(!regex || md != MD_8) return false;	//?!?! Not implemented still for 16/32 modes

#if HAVE_PCRE_2
    PCRE2_SIZE *capv = pcre2_get_ovector_pointer_8((pcre2_match_data_8*)mDt);
#endif

#if HAVE_PCRE_2
    int n = pcre2_match_8((pcre2_code_8*)regex, (PCRE2_SPTR8)vl.data(), vl.size(), (global?lastIndex:0), 0, (pcre2_match_data_8*)mDt, NULL);
#else
    int n = pcre_exec((pcre*)regex, NULL, vl.data(), vl.size(), (global?lastIndex:0), 0, capv, TRegExp_vSz);
#endif
    if(global) lastIndex = (n>0) ? capv[1] : 0;
    return (n>0);
}

int TRegExp::search( const string &vl, int off, int *length )
{
    if(!regex) return -1;

#if HAVE_PCRE_2
    PCRE2_SIZE *capv = pcre2_get_ovector_pointer_8((pcre2_match_data_8*)mDt);
#endif

    int n = 0;
#if HAVE_PCRE_2
    if(md == MD_8) n = pcre2_match_8((pcre2_code_8*)regex, (PCRE2_SPTR8)vl.data(), vl.size(), off, PCRE2_NO_UTF_CHECK, (pcre2_match_data_8*)mDt, NULL);
# if HAVE_PCRE32
    else if(md == MD_32) n = pcre2_match_32((pcre2_code_32*)regex, (PCRE2_SPTR32)vl.data(), vl.size()/4, off, PCRE2_NO_UTF_CHECK, (pcre2_match_data_32*)mDt, NULL);
# endif
# if HAVE_PCRE16
    else if(md == MD_16) n = pcre2_match_16((pcre2_code_16*)regex, (PCRE2_SPTR16)vl.data(), vl.size()/2, off, PCRE2_NO_UTF_CHECK, (pcre2_match_data_16*)mDt, NULL);
# endif
#else
    if(md == MD_8) n = pcre_exec((pcre*)regex, NULL, vl.data(), vl.size(), off, PCRE_NO_UTF8_CHECK, capv, TRegExp_vSz);
# if HAVE_PCRE32
    else if(md == MD_32) n = pcre32_exec((pcre32*)regex, NULL, (PCRE_SPTR32)vl.data(), vl.size()/4, off, PCRE_NO_UTF32_CHECK, capv, TRegExp_vSz);
# endif
# if HAVE_PCRE16
    else if(md == MD_16) n = pcre16_exec((pcre16*)regex, NULL, (PCRE_SPTR16)vl.data(), vl.size()/2, off, PCRE_NO_UTF16_CHECK, capv, TRegExp_vSz);
# endif
#endif

    if(length) *length = (n > 0) ? capv[1]-capv[0] : 0;
    return (n > 0) ? capv[0] : -1;
}

string TRegExp::substExprRepl( const string &str, const string &val, void *icapv_, int n )
{
#if HAVE_PCRE_2
    PCRE2_SIZE *icapv = (PCRE2_SIZE *)icapv_;
#else
    int *icapv = (int *)icapv_;
#endif

    string rez = str;
    for(size_t cpos = 0; n > 0 && (cpos=rez.find("$",cpos)) != string::npos && cpos < (rez.size()-1); )
	switch(rez[cpos+1]) {
	    case '$':	rez.replace(cpos,2,"$"); cpos++; break;
	    case '`':	rez.replace(cpos,2,val,0,icapv[0]); cpos += icapv[0]; break;
	    case '\'':	rez.replace(cpos,2,val,icapv[1],val.size()-icapv[1]);cpos += (val.size()-icapv[1]); break;
	    case '&':	rez.replace(cpos,2,val,icapv[0],(icapv[1]-icapv[0])); cpos += icapv[1]; break;
	    default: {
		int nd = isdigit(rez[cpos+1]) ? 1 : 0;
		if(nd && cpos < (rez.size()-2) && isdigit(rez[cpos+2])) nd++;
		int subexp = s2i(string(rez.data()+cpos+1,nd));
		string replVl;
		if(subexp > 0 && subexp < n) replVl.assign(val,icapv[subexp*2],icapv[subexp*2+1]-icapv[subexp*2]);
		rez.replace(cpos,nd+1,replVl);
		cpos += replVl.size();
	    }
	}
    return rez;
}

TVariant TRegExp::propGet( const string &id )
{
    if(id == "source")		return pattern;
    if(id == "global")		return (bool)global;
    if(id == "ignoreCase")	return (bool)ignoreCase;
    if(id == "multiline")	return (bool)multiline;
    if(id == "ungreedy")	return (bool)ungreedy;
    if(id == "UTF8")		return (bool)UTF8;
    if(id == "lastIndex")	return lastIndex;
    return TVariant();
}

void TRegExp::propSet( const string &id, TVariant val )
{
    if(id == "lastIndex")	lastIndex = val.getI();
}

TVariant TRegExp::funcCall( const string &id, vector<TVariant> &prms )
{
    // Array exec(string val) - call match for string 'val'. Return matched substring (0) and subexpressions (>0) array.
    //    Set array property "index" to matched substring position.
    //    Set array property "input" to source match string.
    //  val - matched string
    if(id == "exec" && prms.size() && prms[0].type() == TVariant::String) return match(prms[0].getS());
    // bool test(string val) - call match for string 'val'. Return "true" for match OK.
    //  val - matched string
    if(id == "test" && prms.size() && prms[0].type() == TVariant::String) return test(prms[0].getS());

    return TVarObj::funcCall(id, prms);
}

string TRegExp::getStrXML( const string &oid )
{
    string nd("<TRegExp");
    if(!oid.empty()) nd += " p='"+oid+"'";
    nd += ">\n";
    nd += "<rule>"+TSYS::strEncode(pattern,TSYS::Html)+"</rule>\n";
    nd = nd+"<flg>"+(global?"g":"")+(ignoreCase?"i":"")+(multiline?"m":"")+(ungreedy?"U":"")+"</flg>\n";
    nd += "</TRegExp>\n";

    return nd;
}

//*************************************************
//* XMLNodeObj - XML node object                  *
//*************************************************
XMLNodeObj::XMLNodeObj( const string &name ) : mName(name), parent(NULL)
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), 1);
}

XMLNodeObj::~XMLNodeObj( )
{
    while(childSize()) childDel(childSize()-1);

    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), -1);
}

string XMLNodeObj::name( )
{
    dataM.lock();
    string rez = mName;
    dataM.unlock();
    return rez;
}

string XMLNodeObj::text( bool full )
{
    dataM.lock();
    string rez = mText;
    for(unsigned iCh = 0; full && iCh < mChilds.size(); iCh++)
	rez += mChilds[iCh].at().text(full);
    dataM.unlock();
    return rez;
}

void XMLNodeObj::setName( const string &vl )
{
    dataM.lock();
    mName = vl;
    dataM.unlock();
}

void XMLNodeObj::setText( const string &vl )
{
    dataM.lock();
    mText = vl;
    dataM.unlock();
}

void XMLNodeObj::childAdd( AutoHD<XMLNodeObj> nd )
{
    if(&nd.at() == this) return;
    dataM.lock();
    mChilds.push_back(nd);
    nd.at().parent = this;
    dataM.unlock();
}

void XMLNodeObj::childIns( unsigned id, AutoHD<XMLNodeObj> nd )
{
    if(&nd.at() == this) return;
    dataM.lock();
    //if(id < 0) id = mChilds.size();
    id = vmin(id, mChilds.size());
    mChilds.insert(mChilds.begin()+id,nd);
    nd.at().parent = this;
    dataM.unlock();
}

void XMLNodeObj::childDel( unsigned id )
{
    if(/*id < 0 || */id >= mChilds.size()) throw TError("XMLNodeObj", _("Error deleting the child '%d'."), id);
    dataM.lock();
    if(mChilds[id].at().parent == this) mChilds[id].at().parent = NULL;
    mChilds.erase(mChilds.begin()+id);
    dataM.unlock();
}

void XMLNodeObj::childDel( AutoHD<XMLNodeObj> nd )
{
    if(nd.at().parent != this) throw TError("XMLNodeObj", _("Error deleting not child."));
    dataM.lock();
    for(unsigned iCh = 0; iCh < mChilds.size(); ++iCh)
	if(&mChilds[iCh].at() == &nd.at()) {
	    mChilds[iCh].at().parent = NULL;
	    mChilds.erase(mChilds.begin()+iCh);
	    break;
	}
    dataM.unlock();
}

AutoHD<XMLNodeObj> XMLNodeObj::childGet( unsigned id )
{
    if(/*id < 0 || */id >= mChilds.size()) throw TError("XMLNodeObj", _("Child '%d' is not allowed."), id);
    dataM.lock();
    AutoHD<XMLNodeObj> rez = mChilds[id];
    dataM.unlock();
    return rez;
}

AutoHD<XMLNodeObj> XMLNodeObj::childGet( const string &name, unsigned num )
{
    dataM.lock();
    AutoHD<XMLNodeObj> rez;
    for(int iCh = 0, iN = 0; iCh < (int)mChilds.size(); iCh++)
	if(strcasecmp(mChilds[iCh].at().name().c_str(),name.c_str()) == 0 && (iN++) == (int)num)
	    rez = mChilds[iCh];
    dataM.unlock();
    if(rez.freeStat()) throw TError("XMLNodeObj", _("Child %s:%d is not found!"), name.c_str(), num);
    return rez;
}

string XMLNodeObj::getStrXML( const string &oid )
{
    string nd("<XMLNodeObj:"+name());
    if(!oid.empty()) nd += " p='"+oid+"'";
    dataM.lock();
    for(map<string,TVariant>::iterator ip = mProps.begin(); ip != mProps.end(); ip++)
	nd += " "+ip->first+"=\""+TSYS::strEncode(ip->second.getS(),TSYS::Html)+"\"";
    nd += ">"+TSYS::strEncode(text(),TSYS::Html)+"\n";
    for(unsigned iCh = 0; iCh < mChilds.size(); iCh++)
	nd += mChilds[iCh].at().getStrXML();
    dataM.unlock();
    nd += "</XMLNodeObj:"+name()+">\n";

    return nd;
}

AutoHD<TVarObj> XMLNodeObj::parseStrXML( XMLNode *nd )
{
    XMLNodeObj *rez = new XMLNodeObj(TSYS::strParse(nd->name(),1,":"));
    rez->mText = nd->text();

    //Attributes process
    vector<string> lst;
    nd->attrList(lst);
    for(unsigned i_l = 0; i_l < lst.size(); i_l++)
	rez->mProps[lst[i_l]] = nd->attr(lst[i_l]);

    //Child nodes process
    for(unsigned iCh = 0; iCh < nd->childSize(); iCh++)
	rez->mChilds.push_back(XMLNodeObj::parseStrXML(nd->childGet(iCh)));

    return rez;
}

TVariant XMLNodeObj::funcCall( const string &id, vector<TVariant> &prms )
{
    // string name( ) - node name
    if(id == "name")	return name();
    // string text( bool full = false ) - node text
    if(id == "text")	return text(prms.size()&&prms[0].getB());
    // string attr(string id) - get node attribute
    //  id - attribute identifier
    if(id == "attr" && prms.size()) {
	TVariant avl = propGet(prms[0].getS());
	return (avl.isEVal() || avl.isNull()) ? "" : avl;
    }
    // XMLNodeObj setName(string vl) - set node name
    //  vl - value for node name
    if((id == "setName" || id == "nameSet") && prms.size())	{ setName(prms[0].getS()); return this; }
    // XMLNodeObj setText(string vl) - set node text
    //  vl - value for node text
    if((id == "setText" || id == "textSet") && prms.size())	{ setText(prms[0].getS()); return this; }
    // XMLNodeObj {setAttr,attrSet}(string id, string vl) - set attribute to value
    //  id - attribute identifier
    //  vl - value for attribute
    if((id == "setAttr" || id == "attrSet") && prms.size() >= 2) { propSet(prms[0].getS(), prms[1].getS()); return this; }
    // XMLNodeObj clear( bool full = false ) - clear the node for the childs remove, text and attributes for <full>.
    //  full - full clear include attributes.
    if(id == "clear") {
	while(childSize()) childDel(0);
	setText("");
	if(prms.size() && prms[0].getB()) propClear();
	return this;
    }
    // int childSize( ) - return childs counter for node
    if(id == "childSize")	return (int)childSize();
    // XMLNodeObj childAdd(ElTp no = XMLNodeObj) - add node <no> as child to the node
    // XMLNodeObj childAdd(string nd) - add node with name <nd>
    //  no - node object or name for new node
    if(id == "childAdd") {
	AutoHD<XMLNodeObj> no;
	if(prms.size() && prms[0].type() == TVariant::Object && !(no=prms[0].getO()).freeStat()) ;
	else if(prms.size()) no = new XMLNodeObj(prms[0].getS());
	else no = new XMLNodeObj();
	childAdd(no);
	return AutoHD<TVarObj>(no);
    }
    // XMLNodeObj childIns(int id, ElTp no = XMLNodeObj) - insert node <no> as child to the node
    // XMLNodeObj childIns(int id, string nd) - insert node with name <nd>
    //  id - insert position
    //  no - node object or name for new node
    if(id == "childIns" && prms.size()) {
	AutoHD<XMLNodeObj> no;
	if(prms.size() > 1 && prms[1].type() == TVariant::Object && !(no=prms[1].getO()).freeStat()) ;
	else if(prms.size() > 1) no = new XMLNodeObj(prms[1].getS());
	else no = new XMLNodeObj();
	childIns(prms[0].getI(), no);
	return AutoHD<TVarObj>(no);
    }
    // XMLNodeObj childDel(int id) - remove child node from position <id>
    // XMLNodeObj childDel(ElTp no) - remove child node <no>
    //  id - child node position
    //  no - node object
    if(id == "childDel" && prms.size()) {
	AutoHD<XMLNodeObj> no;
	if(prms[0].type() == TVariant::Object && !(no=prms[0].getO()).freeStat())
	    childDel(no);
	else childDel(prms[0].getI());
	return this;
    }
    // XMLNodeObj childGet(int id) - get node from position <id>
    // XMLNodeObj childGet(string name, int num = 0) - get node by name <name> and number <num>
    //  id - child node position;
    //  name - node/tag name;
    //  num - node/tag number <num>.
    if(id == "childGet" && prms.size()) {
	if(prms[0].type() == TVariant::String)
	    return AutoHD<TVarObj>(childGet(prms[0].getS(),(prms.size()>1)?prms[1].getI():0));
	return AutoHD<TVarObj>(childGet(prms[0].getI()));
    }
    // XMLNodeObj parent() - get parent node
    if(id == "parent")	return !parent ? TVariant(false) : TVariant(parent);
    // string load(string str, bool file = false, int flg = 0, string cp = "UTF-8") - load XML tree from XML-stream from string or file
    //  str - source stream string or file name, for <file> = true;
    //  file - load XML-tree from file (true) or stram (false);
    //  flg - node's load flags:
    //    0x01 - text and comments load in separated nodes "<*>" and "<!>";
    //    0x02 - no remove spaces for begin and end tag's text.
    //  cp - source codepage.
    if(id == "load" && prms.size()) {
	XMLNode nd;
	string rez = "0";
	//  Load from file
	if(prms.size() >= 2 && prms[1].getB()) {
	    string s_buf;
	    int hd = open(prms[0].getS().c_str(), O_RDONLY);
	    if(hd < 0) return TSYS::strMess(_("2:Error opening the file '%s': %s"), prms[0].getS().c_str(), strerror(errno));
	    bool fOK = true;
	    int cf_sz = lseek(hd, 0, SEEK_END);
	    if(cf_sz > 0) {
		lseek(hd, 0, SEEK_SET);
		char buf[prmStrBuf_SZ];
		for(int len = 0; (len=read(hd,buf,sizeof(buf))) > 0; ) s_buf.append(buf, len);
		fOK = s_buf.size();
	    }
	    if(close(hd) != 0)
		mess_warning("XMLNodeObj", _("Closing the file %d error '%s (%d)'!"), hd, strerror(errno), errno);
	    if(!fOK) return TSYS::strMess(_("3:Error loading the file '%s'."), prms[0].getS().c_str());

	    try { nd.load(s_buf, ((prms.size()>=3)?prms[2].getI():0), ((prms.size()>=4)?prms[3].getS():Mess->charset())); }
	    catch(TError &err) { rez = "1:"+err.mess; }
	}
	//  Load from string
	else
	    try { nd.load(prms[0].getS(), ((prms.size()>=3)?prms[2].getI():0), Mess->charset()); }
	    catch(TError &err) { rez = "1:"+err.mess; }

	fromXMLNode(nd);

	return rez;
    }
    // string save(int flg = 0, string path = "", string cp = "UTF-8") - save XML-tree to string or file stream
    //  flg - save options:
    //   0x01 - interrupt the string before the opening tag;
    //   0x02 - interrupt the string after the opening tag;
    //   0x04 - interrupt the string after a closing tag;
    //   0x08 - interrupt the string after the text;
    //   0x10 - interrupt the string after the instruction;
    //   0x1E - interrupt the string after all.
    //  path - file path for save to file
    //  cp - target codepage
    if(id == "save") {
	XMLNode nd;
	toXMLNode(nd);
	string s_buf = nd.save(((prms.size()>=1)?prms[0].getI():0), (prms.size()>=3)?prms[2].getS():Mess->charset());
	//  Save to file
	if(prms.size() >= 2) {
	    int hd = open(prms[1].getS().c_str(), O_RDWR|O_CREAT|O_TRUNC, SYS->permCrtFiles());
	    if(hd < 0)	return "";
	    bool fOK = (write(hd,s_buf.data(),s_buf.size()) == (int)s_buf.size());
	    if(close(hd) != 0)
		mess_warning("XMLNodeObj", _("Closing the file %d error '%s (%d)'!"), hd, strerror(errno), errno);
	    if(!fOK)	return "";
	}
	return s_buf;
    }
    // XMLNodeObj getElementBy( string val, string attr = "id" ) - get element from the tree by attribute <attr> value <val>.
    //  val - attribute value for find;
    //  attr - attribute name for find it value.
    if(id == "getElementBy" && prms.size())
	return AutoHD<TVarObj>(getElementBy(((prms.size() >= 2)?prms[1].getS():"id"),prms[0].getS()));
    // TArrayObj<XMLNodeObj> getElementsBy( string tag, string attrVal = "", string attr = "id", int limit = 0, int from = 0 ) -
    //    get elements array from the tree by <tag> and attribute <attr> value <attrVal>.
    //  tag - tag name, empty for all;
    //  attrVal - attribute value to find, empty for pass;
    //  attr - attribute name to find it value;
    //  limit - up to the specified items;
    //  from - from the specified item.
    if(id == "getElementsBy" && prms.size()) {
	TArrayObj *rez = new TArrayObj();
	unsigned from = (prms.size() >= 5) ? prms[4].getI() : 0;
	getElementsBy(prms[0].getS(), ((prms.size() >= 3) ? prms[2].getS() : "id"), ((prms.size() >= 2)?prms[1].getS():""), rez,
			((prms.size() >= 4) ? prms[3].getI() : 0), &from);
	return rez;
    }

    return TVarObj::funcCall(id, prms);
}

void XMLNodeObj::toXMLNode( XMLNode &nd )
{
    nd.clear();
    nd.setName(name())->setText(text());
    dataM.lock();
    for(map<string,TVariant>::iterator ip = mProps.begin(); ip != mProps.end(); ip++)
	nd.setAttr(ip->first,ip->second.getS());
    for(unsigned iCh = 0; iCh < mChilds.size(); iCh++)
	mChilds[iCh].at().toXMLNode(*nd.childAdd());
    dataM.unlock();
}

void XMLNodeObj::fromXMLNode( XMLNode &nd )
{
    while(childSize()) childDel(0);

    setName(nd.name());
    setText(nd.text());

    vector<string> alst;
    nd.attrList(alst);
    for(unsigned iA = 0; iA < alst.size(); iA++)
	propSet(alst[iA], nd.attr(alst[iA]));

    for(unsigned iCh = 0; iCh < nd.childSize(); iCh++) {
	XMLNodeObj *xn = new XMLNodeObj();
	childAdd(xn);
	xn->fromXMLNode(*nd.childGet(iCh));
    }
}

AutoHD<XMLNodeObj> XMLNodeObj::getElementBy( const string &attr, const string &val )
{
    if(propGet(attr).getS() == val)	return this;

    AutoHD<XMLNodeObj> rez;
    for(unsigned iCh = 0; rez.freeStat() && iCh < childSize(); iCh++)
	rez = childGet(iCh).at().getElementBy(attr, val);

    return rez;
}

void XMLNodeObj::getElementsBy( const string &tag, const string &attr, const string &val, TArrayObj *rez, unsigned limit, unsigned *from )
{
    if(!tag.size() && (!attr.size() || !val.size()))	return;
    if((!tag.size() || tag == name()) && (!attr.size() || !val.size() || propGet(attr).getS() == val)) {
	if(!from || *from == 0) rez->arSet(-1, this);
	else if(from && *from)	(*from)--;
    }

    dataM.lock();
    for(unsigned iCh = 0; iCh < childSize() && (!limit || rez->arSize() < limit); iCh++)
	childGet(iCh).at().getElementsBy(tag, attr, val, rez, limit, from);
    dataM.unlock();
}

//***********************************************************
//* TCntrNodeObj                                            *
//*   TCntrNode object for access to system's objects       *
//***********************************************************
TCntrNodeObj::TCntrNodeObj( AutoHD<TCntrNode> ind, const string &user_lang ) : mUserLang(user_lang)
{
    cnd = ind;

    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), 1);
}

TCntrNodeObj::~TCntrNodeObj( )
{
    if(mess_lev() == TMess::Debug) SYS->cntrIter(TVarObj::objName()+":"+objName(), -1);
}

string TCntrNodeObj::objName( )
{
    if(cnd.freeStat())	return "TCntrNodeObj:Free";
    return cnd.at().objName();
}

string TCntrNodeObj::user( )	{ return TSYS::strLine(mUserLang, 0); }

string TCntrNodeObj::lang( )	{ return TSYS::strLine(mUserLang, 1); }

TVariant TCntrNodeObj::propGet( const string &id )
{
    if(cnd.freeStat()) return TVariant();
    try {
	AutoHD<TCntrNode> nnd = cnd.at().nodeAt(id);
	return new TCntrNodeObj(nnd, mUserLang);
    } catch(...) { }

    return cnd.at().objPropGet(id);
}

void TCntrNodeObj::propSet( const string &id, TVariant val )
{
    if(cnd.freeStat()) return;
    cnd.at().objPropSet(id, val);
}

string TCntrNodeObj::getStrXML( const string &oid )	{ return "<TCntrNodeObj path=\""+cnd.at().nodePath()+"\"/>"; }

TVariant TCntrNodeObj::funcCall( const string &id, vector<TVariant> &prms )
{
    if(cnd.freeStat()) throw TError("TCntrNodeObj", _("Object is not connected to a node of the OpenSCADA tree."));
    try{ return cnd.at().objFuncCall(id, prms, mUserLang); } catch(TError&){ }
    return TVarObj::funcCall(id, prms);
}
