/************* Tabxml C++ Program Source Code File (.CPP) **************/
/* PROGRAM NAME: TABXML                                                */
/* -------------                                                       */
/*  Version 3.0                                                        */
/*                                                                     */
/*  Author Olivier BERTRAND          2007 - 2020                       */
/*                                                                     */
/*  This program are the XML tables classes using MS-DOM or libxml2.   */
/***********************************************************************/

/***********************************************************************/
/*  Include required compiler header files.                            */
/***********************************************************************/
#include "my_global.h"
#include <m_string.h>
#include <fcntl.h>
#if defined(_WIN32)
#include <io.h>
#include <winsock2.h>
//#include <windows.h>
#include <comdef.h>
#else   // !_WIN32
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
//#include <ctype.h>
#include "osutil.h"
#define _O_RDONLY O_RDONLY
#endif  // !_WIN32
#include "resource.h"                        // for IDS_COLUMNS

#define INCLUDE_TDBXML
#define NODE_TYPE_LIST

/***********************************************************************/
/*  Include application header files:                                  */
/*  global.h    is header containing all global declarations.          */
/*  plgdbsem.h  is header containing the DB application declarations.  */
/*  tabdos.h    is header containing the TABDOS class declarations.    */
/***********************************************************************/
#include "global.h"
#include "plgdbsem.h"
//#include "reldef.h"
#include "xtable.h"
#include "colblk.h"
#include "mycat.h"
#include "xindex.h"
#include "plgxml.h"
#include "tabxml.h"
#include "tabmul.h"

extern "C" char version[];

#if defined(_WIN32) && defined(DOMDOC_SUPPORT)
#define XMLSUP "MS-DOM"
#else   // !_WIN32
#define XMLSUP "libxml2"
#endif  // !_WIN32

#define TYPE_UNKNOWN     12        /* Must be greater than other types */
#define XLEN(M)  sizeof(M) - strlen(M) - 1	       /* To avoid overflow*/

int GetDefaultDepth(void);

/***********************************************************************/
/* Class and structure used by XMLColumns.                             */
/***********************************************************************/
typedef class XMCOL *PXCL;

class XMCOL : public BLOCK {
 public:
  // Constructors
  XMCOL(void) {Next = NULL; 
               Name[0] = 0; 
               Fmt = NULL; 
               Type = 1;
               Len = Scale = 0;
               Cbn = false; 
               Found = true;}
  XMCOL(PGLOBAL g, PXCL xp, char *fmt, int i) {
               Next = NULL; 
               strcpy(Name, xp->Name);
               Fmt = (*fmt) ? PlugDup(g, fmt) : NULL; 
               Type = xp->Type;
               Len = xp->Len;
               Scale = xp->Scale;
               Cbn = (xp->Cbn || i > 1); 
               Found = true;}

  // Members
  PXCL  Next;
  char  Name[64];
  char *Fmt;
  int   Type;
  int   Len;
  int   Scale;
  bool  Cbn;
  bool  Found;
}; // end of class XMCOL

typedef struct LVL {
  PXNODE  pn;
  PXLIST  nl;
  PXATTR  atp;
  bool    b;
  long    k;
  int     m, n;
} *PLVL;

/***********************************************************************/
/* XMLColumns: construct the result blocks containing the description  */
/* of all the columns of a table contained inside an XML file.         */
/***********************************************************************/
PQRYRES XMLColumns(PGLOBAL g, char *db, char *tab, PTOS topt, bool info)
{
  static int  buftyp[] = {TYPE_STRING, TYPE_SHORT, TYPE_STRING, TYPE_INT, 
                          TYPE_INT, TYPE_SHORT, TYPE_SHORT, TYPE_STRING};
  static XFLD fldtyp[] = {FLD_NAME, FLD_TYPE, FLD_TYPENAME, FLD_PREC, 
                          FLD_LENGTH, FLD_SCALE, FLD_NULL, FLD_FORMAT};
  static unsigned int length[] = {0, 6, 8, 10, 10, 6, 6, 0};
  char    colname[65], fmt[129], buf[512];
  int     i, j, lvl, n = 0;
  int     ncol = sizeof(buftyp) / sizeof(int);
  bool    ok = true;
	PCSZ    fn, op;
  PXCL    xcol, xcp, fxcp = NULL, pxcp = NULL;
  PLVL   *lvlp, vp;
  PXNODE  node = NULL;
  PXMLDEF tdp;
  PTDBXML txmp;
  PQRYRES qrp;
  PCOLRES crp;

  if (info) {
    length[0] = 128;
    length[7] = 256;
    goto skipit;
    } // endif info

	if (GetIntegerTableOption(g, topt, "Multiple", 0)) {
		snprintf(g->Message, sizeof(g->Message), "Cannot find column definition for multiple table");
		return NULL;
	}	// endif Multiple

	/*********************************************************************/
  /*  Open the input file.                                             */
  /*********************************************************************/
  if (!(fn = GetStringTableOption(g, topt, "Filename", NULL))) {
    if (topt->http) // REST table can have default filename
      fn = GetStringTableOption(g, topt, "Subtype", NULL);
    
    if (!fn) {
      snprintf(g->Message, sizeof(g->Message), MSG(MISSING_FNAME));
      return NULL;
    } else
      topt->subtype = NULL;

  } // endif fn

  lvl = GetIntegerTableOption(g, topt, "Level", GetDefaultDepth());
  lvl = GetIntegerTableOption(g, topt, "Depth", lvl);
  lvl = (lvl < 0) ? 0 : (lvl > 16) ? 16 : lvl;

  if (trace(1))
    htrc("File %s lvl=%d\n", topt->filename, lvl);

  tdp = new(g) XMLDEF;
  tdp->Fn = fn;

	if (!(tdp->Database = SetPath(g, db)))
		return NULL;

  tdp->Tabname = tab;
	tdp->Tabname = (char*)GetStringTableOption(g, topt, "Tabname", tab);
	tdp->Rowname = (char*)GetStringTableOption(g, topt, "Rownode", NULL);
	tdp->Zipped = GetBooleanTableOption(g, topt, "Zipped", false);
	tdp->Entry = GetStringTableOption(g, topt, "Entry", NULL);
	tdp->Skip = GetBooleanTableOption(g, topt, "Skipnull", false);

  if (!(op = GetStringTableOption(g, topt, "Xmlsup", NULL)))
#if defined(_WIN32)
    tdp->Usedom = true;
#else   // !_WIN32
    tdp->Usedom = false;
#endif  // !_WIN32
  else
    tdp->Usedom = (toupper(*op) == 'M' || toupper(*op) == 'D');

  txmp = new(g) TDBXML(tdp);

  if (txmp->Initialize(g))
    goto err;

  xcol = new(g) XMCOL;
  colname[64] = 0;
  fmt[128] = 0;
  lvlp = (PLVL*)PlugSubAlloc(g, NULL, sizeof(PLVL) * (lvl + 1));

  for (j = 0; j <= lvl; j++)
    lvlp[j] = (PLVL)PlugSubAlloc(g, NULL, sizeof(LVL));

  /*********************************************************************/
  /*  Analyse the XML tree and define columns.                         */
  /*********************************************************************/
  for (i = 1; ; i++) {
    // Get next row
    switch (txmp->ReadDB(g)) {
      case RC_EF:
        vp = NULL;
        break;
      case RC_FX:
        goto err;
      default:
        vp = lvlp[0];
        vp->pn = txmp->RowNode;
        vp->atp = vp->pn->GetAttribute(g, NULL);
        vp->nl = vp->pn->GetChildElements(g);
        vp->b = true;
        vp->k = 0;
        vp->m = vp->n = 0;
        j = 0;
      } // endswitch ReadDB

    if (!vp)
      break;

    while (true) {
      if (!vp->atp &&
          !(node = (vp->nl) ? vp->nl->GetItem(g, vp->k++, tdp->Usedom ?
                                              node : NULL)
            : NULL))
      {
        if (j) {
          vp = lvlp[--j];

          if (!tdp->Usedom)    // nl was destroyed
            vp->nl = vp->pn->GetChildElements(g);

          if (!lvlp[j+1]->b) {
            vp->k--;
            ok = false;
            } // endif b

          continue;
        } else
          break;
      }
      xcol->Name[vp->n] = 0;
      fmt[vp->m] = 0;

     more:
      if (vp->atp) {
			size_t z = sizeof(colname) - 1;
                        size_t xlen= strlen(xcol->Name);
                        strmake(colname, vp->atp->GetName(g), z);
                        strmake(xcol->Name + xlen, colname,
                                sizeof(xcol->Name) - 1 - xlen);

        switch (vp->atp->GetText(g, buf, sizeof(buf))) {
          case RC_INFO:
            PushWarning(g, txmp);
            /* falls through */
          case RC_OK:
            strncat(fmt, "@", XLEN(fmt));
            break;
          default:
            goto err;
          } // enswitch rc

        if (j)
          strncat(fmt, colname, XLEN(fmt));

      } else {
        size_t xlen;
        if (tdp->Usedom && node->GetType() != 1)
          continue;

        xlen= strlen(xcol->Name);
        strmake(colname, node->GetName(g), sizeof(colname)-1);
        strmake(xcol->Name + xlen, colname, sizeof(xcol->Name) - 1 - xlen);

        if (j)
          strncat(fmt, colname, XLEN(fmt));

        if (j < lvl && ok) {
          vp = lvlp[j+1];
          vp->k = 0;
					vp->pn = node;
					vp->atp = node->GetAttribute(g, NULL);
          vp->nl = node->GetChildElements(g);

          if (tdp->Usedom && vp->nl->GetLength() == 1) {
            node = vp->nl->GetItem(g, 0, node);
            vp->b = (node->GetType() == 1);   // Must be ab element
          } else
            vp->b = (vp->nl && vp->nl->GetLength());

          if (vp->atp || vp->b) {
            if (!vp->atp)
							node = vp->nl->GetItem(g, vp->k++, tdp->Usedom ? node : NULL);

						if (!j)
							strncat(fmt, colname, XLEN(fmt));

						strncat(fmt, "/", XLEN(fmt));
						strncat(xcol->Name, "_", XLEN(xcol->Name));
						j++;
            vp->n = (int)strlen(xcol->Name);
            vp->m = (int)strlen(fmt);
            goto more;
          } else {
            vp = lvlp[j];

            if (!tdp->Usedom)    // nl was destroyed
              vp->nl = vp->pn->GetChildElements(g);

          } // endif vp

        } else
          ok = true;

        switch (node->GetContent(g, buf, sizeof(buf))) {
          case RC_INFO:
            PushWarning(g, txmp);
            /* falls through */
          case RC_OK:
						xcol->Cbn = !strlen(buf);
            break;
          default:
            goto err;
          } // enswitch rc

      } // endif atp;

      xcol->Len = strlen(buf);

      // Check whether this column was already found
      for (xcp = fxcp; xcp; xcp = xcp->Next)
        if (!strcmp(xcol->Name, xcp->Name))
          break;
  
      if (xcp) {
        if (xcp->Type != xcol->Type)
          xcp->Type = TYPE_STRING;

        if (*fmt && (!xcp->Fmt || strlen(xcp->Fmt) < strlen(fmt))) {
          xcp->Fmt = PlugDup(g, fmt);
          length[7] = MY_MAX(length[7], strlen(fmt));
          } // endif *fmt

        xcp->Len = MY_MAX(xcp->Len, xcol->Len);
        xcp->Scale = MY_MAX(xcp->Scale, xcol->Scale);
        xcp->Cbn |= (xcol->Cbn || !xcol->Len);
        xcp->Found = true;
      } else if(xcol->Len || !tdp->Skip) {
        // New column
        xcp = new(g) XMCOL(g, xcol, fmt, i);
        length[0] = MY_MAX(length[0], strlen(xcol->Name));
        length[7] = MY_MAX(length[7], strlen(fmt));
  
        if (pxcp) {
          xcp->Next = pxcp->Next;
          pxcp->Next = xcp;
        } else
          fxcp = xcp;

        n++;
      } // endif xcp

			if (xcp)
				pxcp = xcp;

      if (vp->atp)
        vp->atp = vp->atp->GetNext(g);

      } // endwhile

    // Missing column can be null
    for (xcp = fxcp; xcp; xcp = xcp->Next) {
      xcp->Cbn |= !xcp->Found;
      xcp->Found = false;
      } // endfor xcp

    } // endor i

  txmp->CloseDB(g);

 skipit:
  if (trace(1))
    htrc("XMLColumns: n=%d len=%d\n", n, length[0]);

  /*********************************************************************/
  /*  Allocate the structures used to refer to the result set.         */
  /*********************************************************************/
  qrp = PlgAllocResult(g, ncol, n, IDS_COLUMNS + 3,
                          buftyp, fldtyp, length, false, false);

  crp = qrp->Colresp->Next->Next->Next->Next->Next->Next;
  crp->Name = "Nullable";
  crp->Next->Name = "Xpath";

  if (info || !qrp)
    return qrp;

  qrp->Nblin = n;

  /*********************************************************************/
  /*  Now get the results into blocks.                                 */
  /*********************************************************************/
  for (i = 0, xcp = fxcp; xcp; i++, xcp = xcp->Next) {
    if (xcp->Type == TYPE_UNKNOWN)            // Void column
      xcp->Type = TYPE_STRING;

    crp = qrp->Colresp;                    // Column Name
    crp->Kdata->SetValue(xcp->Name, i);
    crp = crp->Next;                       // Data Type
    crp->Kdata->SetValue(xcp->Type, i);
    crp = crp->Next;                       // Type Name
    crp->Kdata->SetValue(GetTypeName(xcp->Type), i);
    crp = crp->Next;                       // Precision
    crp->Kdata->SetValue(xcp->Len, i);
    crp = crp->Next;                       // Length
    crp->Kdata->SetValue(xcp->Len, i);
    crp = crp->Next;                       // Scale (precision)
    crp->Kdata->SetValue(xcp->Scale, i);
    crp = crp->Next;                       // Nullable
    crp->Kdata->SetValue(xcp->Cbn ? 1 : 0, i);
    crp = crp->Next;                       // Field format

    if (crp->Kdata)
      crp->Kdata->SetValue(xcp->Fmt, i);

    } // endfor i

  /*********************************************************************/
  /*  Return the result pointer.                                       */
  /*********************************************************************/
  return qrp;

err:
  txmp->CloseDB(g);
  return NULL;
  } // end of XMLColumns

/* -------------- Implementation of the XMLDEF class  ---------------- */

/***********************************************************************/
/*  Constructor.                                                       */
/***********************************************************************/
XMLDEF::XMLDEF(void)
  {
  Pseudo = 3;
  Fn = NULL;
  Encoding = NULL;
  Tabname = NULL;
  Rowname = NULL;
  Colname = NULL;
  Mulnode = NULL;
  XmlDB = NULL;
  Nslist = NULL;
  DefNs = NULL;
  Attrib = NULL;
  Hdattr = NULL;
	Entry = NULL;
  Coltype = 1;
  Limit = 0;
  Header = 0;
  Xpand = false;
  Usedom = false;
	Zipped = false;
	Mulentries = false;
	Skip = false;
  } // end of XMLDEF constructor

/***********************************************************************/
/*  DefineAM: define specific AM block values from XDB file.           */
/***********************************************************************/
bool XMLDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
  {
	PCSZ defrow, defcol;
	char buf[10];

  Fn = GetStringCatInfo(g, "Filename", NULL);
  Encoding = GetStringCatInfo(g, "Encoding", "UTF-8");

  if (*Fn == '?') {
    snprintf(g->Message, sizeof(g->Message), MSG(MISSING_FNAME));
    return true;
    } // endif fn

  if ((signed)GetIntCatInfo("Flag", -1) != -1) {
    snprintf(g->Message, sizeof(g->Message), MSG(DEPREC_FLAG));
    return true;
    } // endif flag

  defrow = defcol = NULL;
  GetCharCatInfo("Coltype", "", buf, sizeof(buf));

  switch (toupper(*buf)) {
    case 'A':                          // Attribute
    case '@':
    case '0':
      Coltype = 0;
      break;
    case '\0':                         // Default
    case 'T':                          // Tag
    case 'N':                          // Node
    case '1':
      Coltype = 1;
      break;
    case 'C':                          // Column
    case 'P':                          // Position
    case 'H':                          // HTML
    case '2':
      Coltype = 2;
      defrow = "TR";
      defcol = "TD";
      break;
    default:
      snprintf(g->Message, sizeof(g->Message), MSG(INV_COL_TYPE), buf);
      return true;
    } // endswitch typname

  Tabname = GetStringCatInfo(g, "Name", Name);  // Deprecated
  Tabname = GetStringCatInfo(g, "Table_name", Tabname); // Deprecated
  Tabname = GetStringCatInfo(g, "Tabname", Tabname);
  Rowname = GetStringCatInfo(g, "Rownode", defrow);
  Colname = GetStringCatInfo(g, "Colnode", defcol);
  Mulnode = GetStringCatInfo(g, "Mulnode", NULL);
  XmlDB = GetStringCatInfo(g, "XmlDB", NULL);
  Nslist = GetStringCatInfo(g, "Nslist", NULL);
  DefNs = GetStringCatInfo(g, "DefNs", NULL);
  Limit = GetIntCatInfo("Limit", 50);
  Xpand = GetBoolCatInfo("Expand", false);
  Header = GetIntCatInfo("Header", 0);
  GetCharCatInfo("Xmlsup", "*", buf, sizeof(buf));

  // Note that if no support is specified, the default is MS-DOM
  // on Windows and libxml2 otherwise
  if (*buf == '*')
#if defined(_WIN32)
    Usedom = true;
#else   // !_WIN32
    Usedom = false;
#endif  // !_WIN32
  else
    Usedom = (toupper(*buf) == 'M' || toupper(*buf) == 'D');

  // Get eventual table node attribute
  Attrib = GetStringCatInfo(g, "Attribute", NULL);
  Hdattr = GetStringCatInfo(g, "HeadAttr", NULL);

	// Specific for zipped files
	if ((Zipped = GetBoolCatInfo("Zipped", false)))
		Mulentries = ((Entry = GetStringCatInfo(g, "Entry", NULL)))
		? strchr(Entry, '*') || strchr(Entry, '?')
		: GetBoolCatInfo("Mulentries", false);

	return false;
  } // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new TDB of the proper type.                      */
/***********************************************************************/
PTDB XMLDEF::GetTable(PGLOBAL g, MODE m)
  {
  if (Catfunc == FNC_COL)
    return new(g) TDBXCT(this);

	if (Zipped && !(m == MODE_READ || m == MODE_ANY)) {
		snprintf(g->Message, sizeof(g->Message), "ZIpped XML tables are read only");
		return NULL;
	}	// endif Zipped

  PTDBASE tdbp = new(g) TDBXML(this);

  if (Multiple)
    tdbp = new(g) TDBMUL(tdbp);

  return tdbp;
  } // end of GetTable

/* ------------------------- TDBXML Class ---------------------------- */

/***********************************************************************/
/*  Implementation of the TDBXML constuctor.                           */
/***********************************************************************/
TDBXML::TDBXML(PXMLDEF tdp) : TDBASE(tdp)
  {
  Docp = NULL;
  Root = NULL;
  Curp = NULL;
  DBnode = NULL;
  TabNode = NULL;
  RowNode = NULL;
  ColNode = NULL;
  Nlist = NULL;
  Clist = NULL;
  To_Xb = NULL;
  Colp = NULL;
  Xfile = tdp->Fn;
  Enc = tdp->Encoding;
  Tabname = tdp->Tabname;
#if 0 // why all these?
  Rowname = (tdp->Rowname) ? tdp->Rowname : NULL;
  Colname = (tdp->Colname) ? tdp->Colname : NULL;
  Mulnode = (tdp->Mulnode) ? tdp->Mulnode : NULL;
  XmlDB = (tdp->XmlDB) ? tdp->XmlDB : NULL;
  Nslist = (tdp->Nslist) ? tdp->Nslist : NULL;
  DefNs = (tdp->DefNs) ? tdp->DefNs : NULL;
  Attrib = (tdp->Attrib) ? tdp->Attrib : NULL;
  Hdattr = (tdp->Hdattr) ? tdp->Hdattr : NULL;
#endif // 0
	Rowname = tdp->Rowname;
	Colname = tdp->Colname;
	Mulnode = tdp->Mulnode;
	XmlDB = tdp->XmlDB;
	Nslist = tdp->Nslist;
	DefNs = tdp->DefNs;
	Attrib = tdp->Attrib;
	Hdattr = tdp->Hdattr;
	Entry = tdp->Entry;
	Coltype = tdp->Coltype;
  Limit = tdp->Limit;
  Xpand = tdp->Xpand;
	Zipped = tdp->Zipped;
	Mulentries = tdp->Mulentries;
	Changed = false;
  Checked = false;
  NextSame = false;
  NewRow = false;
  Hasnod = false;
  Write = false;
  Bufdone = false;
  Nodedone = false;
  Void = false;
  Usedom = tdp->Usedom;
  Header = tdp->Header;
  Multiple = tdp->Multiple;
  Nrow = -1;
  Irow = Header - 1;
  Nsub = 0;
  N = 0;
  } // end of TDBXML constructor

TDBXML::TDBXML(PTDBXML tdbp) : TDBASE(tdbp)
  {
  Docp = tdbp->Docp;
  Root = tdbp->Root;
  Curp = tdbp->Curp;
  DBnode = tdbp->DBnode;
  TabNode = tdbp->TabNode;
  RowNode = tdbp->RowNode;
  ColNode = tdbp->ColNode;
  Nlist = tdbp->Nlist;
  Clist = tdbp->Clist;
  To_Xb = tdbp->To_Xb;
  Colp = tdbp->Colp;
  Xfile = tdbp->Xfile;
  Enc = tdbp->Enc;
  Tabname = tdbp->Tabname;
  Rowname = tdbp->Rowname;
  Colname = tdbp->Colname;
  Mulnode = tdbp->Mulnode;
  XmlDB = tdbp->XmlDB;
  Nslist = tdbp->Nslist;
  DefNs = tdbp->DefNs;
  Attrib = tdbp->Attrib;
  Hdattr = tdbp->Hdattr;
	Entry = tdbp->Entry;
	Coltype = tdbp->Coltype;
  Limit = tdbp->Limit;
  Xpand = tdbp->Xpand;
	Zipped = tdbp->Zipped;
	Mulentries = tdbp->Mulentries;
	Changed = tdbp->Changed;
  Checked = tdbp->Checked;
  NextSame = tdbp->NextSame;
  NewRow = tdbp->NewRow;
  Hasnod = tdbp->Hasnod;
  Write = tdbp->Write;
  Void = tdbp->Void;
  Usedom = tdbp->Usedom;
  Header = tdbp->Header;
  Multiple = tdbp->Multiple;
  Nrow = tdbp->Nrow;
  Irow = tdbp->Irow;
  Nsub = tdbp->Nsub;
  N = tdbp->N;
  } // end of TDBXML copy constructor

// Used for update
PTDB TDBXML::Clone(PTABS t)
  {
  PTDB    tp;
  PXMLCOL cp1, cp2;
  PGLOBAL g = t->G;

  tp = new(g) TDBXML(this);

  for (cp1 = (PXMLCOL)Columns; cp1; cp1 = (PXMLCOL)cp1->GetNext()) {
    cp2 = new(g) XMLCOL(cp1, tp);  // Make a copy
    NewPointer(t, cp1, cp2);
    } // endfor cp1

  return tp;
  } // end of Clone

/***********************************************************************/
/*  Must not be in tabxml.h because of OEM tables                      */
/***********************************************************************/
const CHARSET_INFO *TDBXML::data_charset()
{
	return &my_charset_utf8mb3_general_ci;
}	// end of data_charset

/***********************************************************************/
/*  Allocate XML column description block.                             */
/***********************************************************************/
PCOL TDBXML::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  if (trace(1))
    htrc("TDBXML: MakeCol %s n=%d\n", (cdp) ? cdp->GetName() : "<null>", n);

  return new(g) XMLCOL(cdp, this, cprec, n);
  } // end of MakeCol

/***********************************************************************/
/*  InsertSpecialColumn: Put a special column ahead of the column list.*/
/***********************************************************************/
PCOL TDBXML::InsertSpecialColumn(PCOL colp)
  {
  if (!colp->IsSpecial())
    return NULL;

//if (Xpand && ((SPCBLK*)colp)->GetRnm())
//  colp->SetKey(0);               // Rownum is no more a key

  colp->SetNext(Columns);
  Columns = colp;
  return colp;
  } // end of InsertSpecialColumn

/***********************************************************************/
/*  LoadTableFile: Load and parse an XML file.                         */
/***********************************************************************/
int TDBXML::LoadTableFile(PGLOBAL g, char *filename)
  {
  int     rc = RC_OK, type = (Usedom) ? TYPE_FB_XML : TYPE_FB_XML2;
  PFBLOCK fp = NULL;
  PDBUSER dup = (PDBUSER)g->Activityp->Aptr;

  if (Docp)
    return rc;               // Already done

  if (trace(1))
    htrc("TDBXML: loading %s\n", filename);

  /*********************************************************************/
  /*  Firstly we check whether this file have been already loaded.     */
  /*********************************************************************/
  if ((Mode == MODE_READ || Mode == MODE_ANY) && !Zipped) 
    for (fp = dup->Openlist; fp; fp = fp->Next)
      if (fp->Type == type && fp->Length && fp->Count)
        if (!stricmp(fp->Fname, filename))
          break;

  if (fp) {
    /*******************************************************************/
    /*  File already loaded. Just increment use count and get pointer. */
    /*******************************************************************/
    fp->Count++;
    Docp = (Usedom) ? GetDomDoc(g, Nslist, DefNs, Enc, fp)
                    : GetLibxmlDoc(g, Nslist, DefNs, Enc, fp);
  } else {
    /*******************************************************************/
    /*  Parse the XML file.                                            */
    /*******************************************************************/
    if (!(Docp = (Usedom) ? GetDomDoc(g, Nslist, DefNs, Enc)
                          : GetLibxmlDoc(g, Nslist, DefNs, Enc)))
      return RC_FX;

    // Initialize the implementation
    if (Docp->Initialize(g, Entry, Zipped)) {
      snprintf(g->Message, sizeof(g->Message), MSG(INIT_FAILED), (Usedom) ? "DOM" : "libxml2");
      return RC_FX;
      } // endif init

    if (trace(1))
      htrc("TDBXML: parsing %s rc=%d\n", filename, rc);

    // Parse the XML file
    if (Docp->ParseFile(g, filename)) {
      // Does the file exist?
      int h= global_open(g, MSGID_NONE, filename, _O_RDONLY);

      if (h != -1) {
        rc = (!_filelength(h)) ? RC_EF : RC_INFO;
        close(h);
      } else
        rc = (errno == ENOENT) ? RC_NF : RC_INFO;

      // Cannot make a Xblock until document is made
      return rc;
      } // endif Docp

    /*******************************************************************/
    /*  Link a Xblock. This make possible to reuse already opened docs */
    /*  and also to automatically close them in case of error g->jump. */
    /*******************************************************************/
    fp = Docp->LinkXblock(g, Mode, rc, filename);
  } // endif xp

  To_Xb = fp;                                  // Useful when closing
  return rc;
  } // end of LoadTableFile

/***********************************************************************/
/*  Initialize the processing of the XML file.                         */
/*  Note: this function can be called several times, eventally before  */
/*  the columns are known (from TBL for instance)                      */
/***********************************************************************/
bool TDBXML::Initialize(PGLOBAL g)
  {
  int     rc;
  PXMLCOL colp;

  if (Void)
    return false;

  if (Columns) {
    // Allocate the buffers that will contain node values
    for (colp = (PXMLCOL)Columns; colp; colp = (PXMLCOL)colp->GetNext())
			if (!colp->IsSpecial()) {            // Not a pseudo column
				if (!Bufdone && colp->AllocBuf(g, Mode == MODE_INSERT))
					return true;

				colp->Nx = colp->Sx = -1;
			} // endif Special

    Bufdone = true;
    } // endif Bufdone

#if !defined(UNIX)
	if (!Root) try {
#else
	if (!Root) {
#endif
		char tabpath[64], filename[_MAX_PATH];

		//  We used the file name relative to recorded datapath
		PlugSetPath(filename, Xfile, GetPath());

		// Load or re-use the table file
		rc = LoadTableFile(g, filename);

		if (rc == RC_OK) {
			// Get root node
			if (!(Root = Docp->GetRoot(g))) {
				// This should never happen as load should have failed
				snprintf(g->Message, sizeof(g->Message), MSG(EMPTY_DOC));
				goto error;
			} // endif Root

		// If tabname is not an Xpath,
		// construct one that will find it anywhere
			if (!strchr(Tabname, '/'))
				snprintf(tabpath, sizeof(tabpath), "//%s", Tabname);
			else
				snprintf(tabpath, sizeof(tabpath), "%s", Tabname);

			// Evaluate table xpath
			if ((TabNode = Root->SelectSingleNode(g, tabpath))) {
				if (TabNode->GetType() != XML_ELEMENT_NODE) {
					snprintf(g->Message, sizeof(g->Message), MSG(BAD_NODE_TYPE), TabNode->GetType());
					goto error;
				} // endif Type

			} else if (Mode == MODE_INSERT && XmlDB) {
				// We are adding a new table to a multi-table file

				// If XmlDB is not an Xpath,
				// construct one that will find it anywhere
				if (!strchr(XmlDB, '/'))
					strcat(strcpy(tabpath, "//"), XmlDB);
				else
					strcpy(tabpath, XmlDB);

				if (!(DBnode = Root->SelectSingleNode(g, tabpath))) {
					// DB node does not exist yet; we cannot create it
					// because we don't know where it should be placed
					snprintf(g->Message, sizeof(g->Message), MSG(MISSING_NODE), XmlDB, Xfile);
					goto error;
				} // endif DBnode

				if (!(TabNode = DBnode->AddChildNode(g, Tabname))) {
					snprintf(g->Message, sizeof(g->Message), MSG(FAIL_ADD_NODE), Tabname);
					goto error;
				} // endif TabNode

				DBnode->AddText(g, "\n");
			} else {
				TabNode = Root;              // Try this ?
				Tabname = TabNode->GetName(g);
			} // endif's

		} else if (rc == RC_NF || rc == RC_EF) {
			// The XML file does not exist or is void
			if (Mode == MODE_INSERT) {
				// New Document
				char buf[64];

				// Create the XML node
				if (Docp->NewDoc(g, "1.0")) {
					snprintf(g->Message, sizeof(g->Message), MSG(NEW_DOC_FAILED));
					goto error;
				} // endif NewDoc

			//  Now we can link the Xblock
				To_Xb = Docp->LinkXblock(g, Mode, rc, filename);

				// Add a CONNECT comment node
				snprintf(buf, sizeof(buf), " Created by the MariaDB CONNECT Storage Engine");
				Docp->AddComment(g, buf);

				if (XmlDB) {
					// This is a multi-table file
					DBnode = Root = Docp->NewRoot(g, XmlDB);
					DBnode->AddText(g, "\n");
					TabNode = DBnode->AddChildNode(g, Tabname);
					DBnode->AddText(g, "\n");
				} else
					TabNode = Root = Docp->NewRoot(g, Tabname);

				if (TabNode == NULL || Root == NULL) {
					snprintf(g->Message, sizeof(g->Message), MSG(XML_INIT_ERROR));
					goto error;
				} else if (SetTabNode(g))
					goto error;

			} else {
				snprintf(g->Message, sizeof(g->Message), MSG(FILE_UNFOUND), Xfile);

				if (Mode == MODE_READ) {
					PushWarning(g, this);
					Void = true;
				} // endif Mode

				goto error;
			} // endif Mode

		} else if (rc == RC_INFO) {
			// Loading failed
			snprintf(g->Message, sizeof(g->Message), MSG(LOADING_FAILED), Xfile);
			goto error;
		} else // (rc == RC_FX)
			goto error;

		if (!Rowname) {
			for (PXNODE n = TabNode->GetChild(g); n; n = n->GetNext(g))
				if (n->GetType() == XML_ELEMENT_NODE) {
					Rowname = n->GetName(g);
					break;
				} // endif Type

			if (!Rowname)
				Rowname = TabNode->GetName(g);
		} // endif Rowname

			// Get row node list
		if (strcmp(Rowname, Tabname))
			Nlist = TabNode->SelectNodes(g, Rowname);
		else
			Nrow = 1;


		Docp->SetNofree(true);       // For libxml2
#if defined(_WIN32)
	} catch (_com_error e) {
    // We come here if a DOM command threw an error
    char   buf[128];

    rc = WideCharToMultiByte(CP_ACP, 0, e.Description(), -1,
                             buf, sizeof(buf), NULL, NULL);

    if (rc)
      snprintf(g->Message, sizeof(g->Message), "%s: %s", MSG(COM_ERROR), buf);
    else
      snprintf(g->Message, sizeof(g->Message), "%s hr=%x", MSG(COM_ERROR), e.Error());

    goto error;
#endif   // _WIN32
#if !defined(UNIX)
  } catch(...) {
    // Other errors
    snprintf(g->Message, sizeof(g->Message), MSG(XMLTAB_INIT_ERR));
    goto error;
#endif
  } // end of try-catches

  if (Root && Columns && (Multiple || !Nodedone)) {
    // Allocate class nodes to avoid dynamic allocation
    for (colp = (PXMLCOL)Columns; colp; colp = (PXMLCOL)colp->GetNext())
      if (!colp->IsSpecial())            // Not a pseudo column
        colp->AllocNodes(g, Docp);

    Nodedone = true;
    } // endif Nodedone

  if (Nrow < 0)
    Nrow = (Nlist) ? Nlist->GetLength() : 0;
 
  // Init is Ok
  return false;

error:
  if (Docp)
    Docp->CloseDoc(g, To_Xb);

  return !Void;
} // end of Initialize

/***********************************************************************/
/*  Set TabNode attributes or header.                                  */
/***********************************************************************/
bool TDBXML::SetTabNode(PGLOBAL g)
  {
  assert(Mode == MODE_INSERT);

  if (Attrib)
    SetNodeAttr(g, Attrib, TabNode);

  if (Header) {
    PCOLDEF cdp;
    PXNODE  rn, cn;

    if (Rowname) {
      TabNode->AddText(g, "\n\t");
      rn = TabNode->AddChildNode(g, Rowname, NULL);
    } else {
      snprintf(g->Message, sizeof(g->Message), MSG(NO_ROW_NODE));
      return true;
    } // endif Rowname

    if (Hdattr)
      SetNodeAttr(g, Hdattr, rn);

    for (cdp = To_Def->GetCols(); cdp; cdp = cdp->GetNext()) {
      rn->AddText(g, "\n\t\t");
      cn = rn->AddChildNode(g, "TH", NULL);
      cn->SetContent(g, (char *)cdp->GetName(), 
                         strlen(cdp->GetName()) + 1);
      } // endfor cdp

    rn->AddText(g, "\n\t");
    } // endif ColType

  return false;
  } // end of SetTabNode

/***********************************************************************/
/*  Set attributes of a table or header node.                          */
/***********************************************************************/
void TDBXML::SetNodeAttr(PGLOBAL g, char *attr, PXNODE node)
  {
  char  *p, *pa, *pn = attr;
  PXATTR an;

  do {
    if ((p = strchr(pn, '='))) {
      pa = pn;
      *p++ = 0;
  
      if ((pn =   strchr(p, ';')))
        *pn++ = 0;
  
      an = node->AddProperty(g, pa, NULL);
      an->SetText(g, p, strlen(p) + 1);
    } else
      break;

    } while (pn);

  } // end of SetNodeAttr

/***********************************************************************/
/*  XML Cardinality: returns table cardinality in number of rows.      */
/*  This function can be called with a null argument to test the       */
/*  availability of Cardinality implementation (1 yes, 0 no).          */
/***********************************************************************/
int TDBXML::Cardinality(PGLOBAL g)
  {
  if (!g)
    return (Multiple || Xpand || Coltype == 2) ? 0 : 1;

  if (Multiple)
    return 10;

  if (Nrow < 0)
    if (Initialize(g))
      return -1;

  return (Void) ? 0 : Nrow - Header;
  } // end of Cardinality

/***********************************************************************/
/*  XML GetMaxSize: returns the number of tables in the database.      */
/***********************************************************************/
int TDBXML::GetMaxSize(PGLOBAL g)
  {
  if (MaxSize < 0) {
    if (!Multiple)
      MaxSize = Cardinality(g) * ((Xpand) ? Limit : 1);
    else
      MaxSize = 10;

    } // endif MaxSize

  return MaxSize;
  } // end of GetMaxSize

/***********************************************************************/
/*  Return the position in the table.                                  */
/***********************************************************************/
int TDBXML::GetRecpos(void)
  {
  union {
    uint Rpos;
    BYTE Spos[4];
    };

  Rpos = htonl(Irow);
  Spos[0] = (BYTE)Nsub;
  return Rpos;
  } // end of GetRecpos

/***********************************************************************/
/*  RowNumber: return the ordinal number of the current row.           */
/***********************************************************************/
int TDBXML::RowNumber(PGLOBAL g, bool b)
  {
  if (To_Kindex && (Xpand || Coltype == 2) && !b) {
    /*******************************************************************/
    /*  Don't know how to retrieve RowID for expanded XML tables.      */
    /*******************************************************************/
    snprintf(g->Message, sizeof(g->Message), MSG(NO_ROWID_FOR_AM),
                        GetAmName(g, GetAmType()));
    return 0;          // Means error
  } else
    return (b || !(Xpand || Coltype == 2)) ? Irow - Header + 1 : N;

  } // end of RowNumber

/***********************************************************************/
/*  XML Access Method opening routine.                                 */
/***********************************************************************/
bool TDBXML::OpenDB(PGLOBAL g)
  {
  if (Use == USE_OPEN) {
    /*******************************************************************/
    /*  Table already open replace it at its beginning.                */
    /*******************************************************************/
    if (!To_Kindex) {
      Irow = Header - 1;
      Nsub = 0;
    } else
      /*****************************************************************/
      /*  Table is to be accessed through a sorted index table.        */
      /*****************************************************************/
      To_Kindex->Reset();

    return false;
    } // endif use

  /*********************************************************************/
  /*  OpenDB: initialize the XML file processing.                      */
  /*********************************************************************/
  Write = (Mode == MODE_INSERT || Mode == MODE_UPDATE);

  if (Initialize(g))
    return true;

  NewRow = (Mode == MODE_INSERT);
  Nsub = 0;
  Use = USE_OPEN;       // Do it now in case we are recursively called
  return false;
  } // end of OpenDB

/***********************************************************************/
/*  Data Base read routine for XML access method.                      */
/***********************************************************************/
int TDBXML::ReadDB(PGLOBAL g)
  {
  bool same;

  if (Void)
    return RC_EF;

  /*********************************************************************/
  /*  Now start the pseudo reading process.                            */
  /*********************************************************************/
  if (To_Kindex) {
    /*******************************************************************/
    /*  Reading is by an index table.                                  */
    /*******************************************************************/
    union {
      uint Rpos;
      BYTE Spos[4];
      };

    int recpos = To_Kindex->Fetch(g);

    switch (recpos) {
      case -1:           // End of file reached
        return RC_EF;
      case -2:           // No match for join
        return RC_NF;
      case -3:           // Same record as last non null one
        same = true;
        return RC_OK;
      default:
        Rpos = recpos;
        Nsub = Spos[0];
        Spos[0] = 0;

        if (Irow != (signed)ntohl(Rpos)) {
          Irow = ntohl(Rpos);
          same = false;
        } else
          same = true;

      } // endswitch recpos

  } else {
    if (trace(1))
      htrc("TDBXML ReadDB: Irow=%d Nrow=%d\n", Irow, Nrow);

    // This is to force the table to be expanded when constructing
    // an index for which the expand column is not specified.
    if (Colp && Irow >= Header) {
      Colp->Eval(g);
      Colp->Reset();
      } // endif Colp

    if (!NextSame) {
      if (++Irow == Nrow)
        return RC_EF;

      same = false;
      Nsub = 0;
    } else {
      // Not sure the multiple column read will be called
      NextSame = false;
      same = true;
      Nsub++;
    } // endif NextSame

    N++;                          // RowID
  } // endif To_Kindex

  if (!same) {
    if (trace(2))
      htrc("TDBXML ReadDB: Irow=%d RowNode=%p\n", Irow, RowNode);

    // Get the new row node
		if (Nlist) {
			if ((RowNode = Nlist->GetItem(g, Irow, RowNode)) == NULL) {
				snprintf(g->Message, sizeof(g->Message), MSG(MISSING_ROWNODE), Irow);
				return RC_FX;
			} // endif RowNode

		} else
			RowNode = TabNode;

    if (Colname && Coltype == 2)
      Clist = RowNode->SelectNodes(g, Colname, Clist);

    } // endif same

  return RC_OK;
  } // end of ReadDB

/***********************************************************************/
/*  CheckRow: called On Insert and Update. Must create the Row node    */
/*  if it does not exist (Insert) and update the Clist if called by    */
/*  a column having an Xpath because it can use an existing node that  */
/*  was added while inserting or Updating this row.                    */
/***********************************************************************/
bool TDBXML::CheckRow(PGLOBAL g, bool b)
  {
  if (NewRow && Mode == MODE_INSERT)
  {
    if (Rowname) {
      TabNode->AddText(g, "\n\t");
      RowNode = TabNode->AddChildNode(g, Rowname, RowNode);
    } else {
      snprintf(g->Message, sizeof(g->Message), MSG(NO_ROW_NODE));
      return true;
    } // endif Rowname
  }

  if (Colname && (NewRow || b))
    Clist = RowNode->SelectNodes(g, Colname, Clist);

  return NewRow = false;
  } // end of CheckRow

/***********************************************************************/
/*  WriteDB: Data Base write routine for XDB access methods.           */
/***********************************************************************/
int TDBXML::WriteDB(PGLOBAL g)
  {
  if (Mode == MODE_INSERT) {
    if (Hasnod)
      RowNode->AddText(g, "\n\t");

    NewRow = true;
    } // endif Mode

  // Something was changed in the document
  Changed = true;
  return RC_OK;
  } // end of WriteDB

/***********************************************************************/
/*  Data Base delete line routine for XDB access methods.              */
/***********************************************************************/
int TDBXML::DeleteDB(PGLOBAL g, int irc)
  {
	// TODO: Handle null Nlist
  if (irc == RC_FX) {
    // Delete all rows
    for (Irow = 0; Irow < Nrow; Irow++)
      if ((RowNode = Nlist->GetItem(g, Irow, RowNode)) == NULL) {
        snprintf(g->Message, sizeof(g->Message), MSG(MISSING_ROWNODE), Irow);
        return RC_FX;
      } else {
        TabNode->DeleteChild(g, RowNode);

        if (Nlist->DropItem(g, Irow))
          return RC_FX;

      } // endif RowNode

    Changed = true;
  } else if (irc != RC_EF) {
    TabNode->DeleteChild(g, RowNode);

    if (Nlist->DropItem(g, Irow))
      return RC_FX;

    Changed = true;
  } // endif's irc

  return RC_OK;
  } // end of DeleteDB

/***********************************************************************/
/*  Data Base close routine for XDB access methods.                    */
/***********************************************************************/
void TDBXML::CloseDB(PGLOBAL g)
  {
  if (Docp) {
    if (Changed) {
      char filename[_MAX_PATH];

      // We used the file name relative to recorded datapath
      PlugSetPath(filename, Xfile, GetPath());

      if (Mode == MODE_INSERT)
        TabNode->AddText(g, "\n");

      // Save the modified document
      if (Docp->DumpDoc(g, filename)) {
        PushWarning(g, this);
        Docp->CloseDoc(g, To_Xb);

        // This causes a crash in Diagnostics_area::set_error_status
//				throw (int)TYPE_AM_XML;
			} // endif DumpDoc
      
      } // endif Changed

    // Free the document and terminate XML processing
    Docp->CloseDoc(g, To_Xb);
    } // endif docp

  if (Multiple) {
    // Reset all constants to start a new parse
    Docp = NULL;
    Root = NULL;
    Curp = NULL;
    DBnode = NULL;
    TabNode = NULL;
    RowNode = NULL;
    ColNode = NULL;
    Nlist = NULL;
    Clist = NULL;
    To_Xb = NULL;
    Colp = NULL;
    Changed = false;
    Checked = false;
    NextSame = false;
    NewRow = false;
    Hasnod = false;
    Write = false;
    Nodedone = false;
    Void = false;
    Nrow = -1;
    Irow = Header - 1;
    Nsub = 0;
    N = 0;
    } // endif Multiple

  } // end of CloseDB

// ------------------------ XMLCOL functions ----------------------------

/***********************************************************************/
/*  XMLCOL public constructor.                                        */
/***********************************************************************/
XMLCOL::XMLCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PCSZ am)
      : COLBLK(cdp, tdbp, i)
  {
  if (cprec) {
    Next = cprec->GetNext();
    cprec->SetNext(this);
  } else {
    Next = tdbp->GetColumns();
    tdbp->SetColumns(this);
  } // endif cprec

  // Set additional XML access method information for column.
  Tdbp = (PTDBXML)tdbp;
  Nl = NULL;
  Nlx = NULL;
  ColNode = NULL;
  ValNode = NULL;
  Cxnp = NULL;
  Vxnp = NULL;
  Vxap = NULL;
  AttNode = NULL;
  Nodes = NULL;
  Nod = 0;
  Inod = -1;
  Mul = false;
  Checked = false;
  Xname = cdp->GetFmt();
  Long = cdp->GetLong();
  Rank = cdp->GetOffset();
  Type = Tdbp->Coltype;
  Nx = -1;
  Sx = -1;
  N = 0;
  Valbuf = NULL;
  To_Val = NULL;
  } // end of XMLCOL constructor

/***********************************************************************/
/*  XMLCOL constructor used for copying columns.                       */
/*  tdbp is the pointer to the new table descriptor.                   */
/***********************************************************************/
XMLCOL::XMLCOL(XMLCOL *col1, PTDB tdbp) : COLBLK(col1, tdbp)
  {
  Tdbp = col1->Tdbp;
  Nl = col1->Nl;
  Nlx = col1->Nlx;
  ColNode = col1->ColNode;
  ValNode = col1->ValNode;
  Cxnp = col1->Cxnp;
  Vxnp = col1->Vxnp;
  Vxap = col1->Vxap;
  AttNode = col1->AttNode;
  Nodes = col1->Nodes;
  Nod = col1->Nod;
  Inod = col1->Inod;
  Mul = col1->Mul;
  Checked = col1->Checked;
  Xname = col1->Xname;
  Valbuf = col1->Valbuf;
  Long = col1->Long;
  Rank = col1->Rank;
  Nx = col1->Nx;
  Sx = col1->Sx;
  N = col1->N;
  Type = col1->Type;
  To_Val = col1->To_Val;
  } // end of XMLCOL copy constructor

/***********************************************************************/
/*  Allocate a buffer of the proper size.                              */
/***********************************************************************/
bool XMLCOL::AllocBuf(PGLOBAL g, bool mode)
  {
  if (Valbuf)
    return false;                       // Already done

  return ParseXpath(g, mode);
  } // end of AllocBuf

/***********************************************************************/
/*  Parse the eventual passed Xpath information.                       */
/*  This information can be specified in the Xpath (or Fieldfmt)       */
/*  column option when creating the table. It permits to indicate the  */
/*  position of the node corresponding to that column in a Xpath-like  */
/*  language (but not a truly compliant one).                          */
/***********************************************************************/
bool XMLCOL::ParseXpath(PGLOBAL g, bool mode)
  {
  char *p, *p2, *pbuf = NULL;
  int   i, n = 1, len = strlen(Name);

  len += ((Tdbp->Colname) ? strlen(Tdbp->Colname) : 0);
  len += ((Xname) ? strlen(Xname) : 0);
  pbuf = (char*)PlugSubAlloc(g, NULL, len + 3);
  *pbuf = '\0';

  if (!mode)
    // Take care of an eventual extra column node a la html
    if (Tdbp->Colname) {
      char *p = strstr(Tdbp->Colname, "%d");
      if (p)
        snprintf(pbuf, len + 3, "%.*s%d%s/", (int) (p - Tdbp->Colname), Tdbp->Colname,
            Rank + (Tdbp->Usedom ? 0 : 1), p + 2);
      else
        snprintf(pbuf, len + 3, "%s/", Tdbp->Colname);
    } // endif Colname

  if (Xname) {
    if (Type == 2) {
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_COL_XPATH), Name, Tdbp->Name);
      return true;
    } else
      strcat(pbuf, Xname);

    if (trace(1))
      htrc("XMLCOL: pbuf=%s\n", pbuf);

    // For Update or Insert the Xpath must be analyzed
    if (mode) {
      for (i = 0, p = pbuf; (p = strchr(p, '/')); i++, p++)
        Nod++;                       // One path node found

      if (Nod)
        Nodes = (char**)PlugSubAlloc(g, NULL, Nod * sizeof(char*));

      } // endif mode

    // Analyze the Xpath for this column
    for (i = 0, p = pbuf; (p2 = strchr(p, '/')); i++, p = p2 + 1) {
      if (Tdbp->Mulnode && !strncmp(p, Tdbp->Mulnode, p2 - p))
      {
        if (!Tdbp->Xpand && mode) {
          snprintf(g->Message, sizeof(g->Message), MSG(CONCAT_SUBNODE));
          return true;
        } else
          Inod = i;                  // Index of multiple node
      }

      if (mode) {
        // For Update or Insert the Xpath must be explicit
        if (strchr("@/.*", *p)) {
          snprintf(g->Message, sizeof(g->Message), MSG(XPATH_NOT_SUPP), Name);
          return true;
        } else
          Nodes[i] = p;

        *p2 = '\0';
        } // endif mode

      } // endfor i, p

    if (*p == '/' || *p == '.') {
      snprintf(g->Message, sizeof(g->Message), MSG(XPATH_NOT_SUPP), Name);
      return true;
    } else if (*p == '@') {
      p++;                           // Remove the @ if mode
      Type = 0;                      // Column is an attribute
    } else
      Type = 1;                      // Column is a node

    if (!*p)
      strcpy(p, Name);               // Xname is column name

    if (Type && Tdbp->Mulnode && !strcmp(p, Tdbp->Mulnode))
      Inod = Nod;                    // Index of multiple node

    if (mode)                        // Prepare Xname
      pbuf = p;

  } else if (Type == 2) {
    // HTML like table, columns are retrieved by position
    new(this) XPOSCOL(Value);        // Change the class of this column
    Inod = -1;
  } else if (Type == 0 && !mode) {
    strcat(strcat(pbuf, "@"), Name);
  } else {                           // Type == 1
    if (Tdbp->Mulnode && !strcmp(Name, Tdbp->Mulnode))
      Inod = 0;                      // Nod

    strcat(pbuf, Name);
  } // endif,s

  if (Inod >= 0) {
    Tdbp->Colp = this;               // To force expand

    if (Tdbp->Xpand)
      n = Tdbp->Limit;

    new(this) XMULCOL(Value);        // Change the class of this column
    } // endif Inod

  Valbuf = (char*)PlugSubAlloc(g, NULL, n * (Long + 1));

  for (i = 0; i < n; i++)
    Valbuf[Long + (i * (Long + 1))] = '\0';

  if (Type || Nod)
    Tdbp->Hasnod = true;

  if (trace(1))
    htrc("XMLCOL: Xname=%s\n", pbuf);

  // Save the calculated Xpath
  Xname = pbuf;
  return false;
  } // end of ParseXpath

/***********************************************************************/
/*  SetBuffer: prepare a column block for write operation.             */
/***********************************************************************/
bool XMLCOL::SetBuffer(PGLOBAL g, PVAL value, bool ok, bool check)
  {
  if (!(To_Val = value)) {
    snprintf(g->Message, sizeof(g->Message), MSG(VALUE_ERROR), Name);
    return true;
  } else if (Buf_Type == value->GetType()) {
    // Values are of the (good) column type
    if (Buf_Type == TYPE_DATE) {
      // If any of the date values is formatted
      // output format must be set for the receiving table
      if (GetDomain() || ((DTVAL *)value)->IsFormatted())
        goto newval;          // This will make a new value;

    } else if (Buf_Type == TYPE_DOUBLE)
      // Float values must be written with the correct (column) precision
      // Note: maybe this should be forced by ShowValue instead of this ?
      value->SetPrec(GetScale());

    Value = value;            // Directly access the external value
  } else {
    // Values are not of the (good) column type
    if (check) {
      snprintf(g->Message, sizeof(g->Message), MSG(TYPE_VALUE_ERR), Name,
              GetTypeName(Buf_Type), GetTypeName(value->GetType()));
      return true;
      } // endif check

 newval:
    if (InitValue(g))         // Allocate the matching value block
      return true;

  } // endif's Value, Buf_Type

  // Because Colblk's have been made from a copy of the original TDB in
  // case of Update, we must reset them to point to the original one.
  if (To_Tdb->GetOrig()) {
    To_Tdb = (PTDB)To_Tdb->GetOrig();
    Tdbp = (PTDBXML)To_Tdb;   // Specific of XMLCOL

    // Allocate the XML buffer
    if (AllocBuf(g, true))      // In Write mode
      return true;

    } // endif GetOrig

  // Set the Column
  Status = (ok) ? BUF_EMPTY : BUF_NO;
  return false;
  } // end of SetBuffer

/***********************************************************************/
/*  Alloc the nodes that will be used during the whole process.        */
/***********************************************************************/
void XMLCOL::AllocNodes(PGLOBAL g, PXDOC dp)
{
  Cxnp = dp->NewPnode(g);
  Vxnp = dp->NewPnode(g);
  Vxap = dp->NewPattr(g);
} // end of AllocNodes

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the column node    */
/*  from the corresponding table, extract from it the node text and    */
/*  convert it to the column type.                                     */
/***********************************************************************/
void XMLCOL::ReadColumn(PGLOBAL g)
  {
  if (Nx == Tdbp->Irow)
    return;                         // Same row than the last read

  ValNode = Tdbp->RowNode->SelectSingleNode(g, Xname, Vxnp);

  if (ValNode) {
    if (ValNode->GetType() != XML_ELEMENT_NODE &&
        ValNode->GetType() != XML_ATTRIBUTE_NODE) {
      snprintf(g->Message, sizeof(g->Message), MSG(BAD_VALNODE), ValNode->GetType(), Name);
			throw (int)TYPE_AM_XML;
		} // endif type

    // Get the Xname value from the XML file
    switch (ValNode->GetContent(g, Valbuf, Long + 1)) {
      case RC_OK:
        break;
      case RC_INFO:
        PushWarning(g, Tdbp);
        break;
      default:
				throw (int)TYPE_AM_XML;
		} // endswitch

    Value->SetValue_psz(Valbuf);
  } else {
    if (Nullable)
      Value->SetNull(true);

    Value->Reset();              // Null value
  } // endif ValNode

  Nx = Tdbp->Irow;
  } // end of ReadColumn

/***********************************************************************/
/*  WriteColumn: what this routine does is to access the last row of   */
/*  the corresponding table, and rewrite the content corresponding     */
/*  to this column node from the column buffer and type.               */
/***********************************************************************/
void XMLCOL::WriteColumn(PGLOBAL g)
  {
  char  *p, buf[16];
  int    done = 0;
  int   i, n, k = 0;
  PXNODE TopNode = NULL;

  if (trace(2))
    htrc("XML WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
          Name, Tdbp->GetTdb_No(), ColUse, Status);

  /*********************************************************************/
  /*  Check whether this node must be written.                         */
  /*********************************************************************/
  if (Value != To_Val)
    Value->SetValue_pval(To_Val, false);    // Convert the updated value

  /*********************************************************************/
  /*  If a check pass was done while updating, all node contruction    */
  /*  has been already one.                                            */
  /*********************************************************************/
  if (Status && Tdbp->Checked && !Value->IsNull()) {
    assert (ColNode != NULL);
    assert ((Type ? (void *)ValNode : (void *)AttNode) != NULL);
    goto fin;
    } // endif Checked

  /*********************************************************************/
  /*  On Insert, a Row node must be created for each row;              */
  /*  For columns having an Xpath, the Clist must be updated.          */
  /*********************************************************************/
  if (Tdbp->CheckRow(g, Nod || Tdbp->Colname))
		throw (int)TYPE_AM_XML;

  /*********************************************************************/
  /*  Null values are represented by no node.                          */
  /*********************************************************************/
	if (Value->IsNull())
    return;

  /*********************************************************************/
  /*  Find the column and value nodes to update or insert.             */
  /*********************************************************************/
  if (Tdbp->Clist) {
    n =  Tdbp->Clist->GetLength();
    ColNode = NULL;
  } else {
    n = 1;
    ColNode = Tdbp->RowNode->Clone(g, ColNode);
  } // endif Clist

  ValNode = NULL;

  for (i = 0; i < n; i++) {
    if (Tdbp->Clist)
      ColNode = Tdbp->Clist->GetItem(g, i, Cxnp);

    /*******************************************************************/
    /*  Check whether an Xpath was provided to go to the column node.  */
    /*******************************************************************/
    for (k = 0; k < Nod; k++)
      if ((ColNode = ColNode->SelectSingleNode(g, Nodes[k], Cxnp)))
        TopNode = ColNode;
      else
        break;

    if (ColNode)
    {
      if (Type)
        ValNode = ColNode->SelectSingleNode(g, Xname, Vxnp);
      else
        AttNode = ColNode->GetAttribute(g, Xname, Vxap);
    }

    if (TopNode || ValNode || AttNode)
      break;                      // We found the good column
    else if (Tdbp->Clist)
      ColNode = NULL;

    // refresh CList in case its Listp was freed in SelectSingleNode above
    if (Tdbp->Clist)
      Tdbp->RowNode->SelectNodes(g, Tdbp->Colname, Tdbp->Clist);
    } // endfor i

  /*********************************************************************/
  /*  Create missing nodes.                                            */
  /*********************************************************************/
  if (ColNode == NULL) {
    if (TopNode == NULL)
    {
      if (Tdbp->Clist) {
        Tdbp->RowNode->AddText(g, "\n\t\t");
        ColNode = Tdbp->RowNode->AddChildNode(g, Tdbp->Colname);
        done = 2;
        TopNode = ColNode;
      } else
        TopNode = Tdbp->RowNode;
    }
    for (; k < Nod && TopNode; k++) {
      if (!done) {
        TopNode->AddText(g, "\n\t\t");
        done = 1;
        } // endif done

      ColNode = TopNode->AddChildNode(g, Nodes[k], Cxnp);
      TopNode = ColNode;
      } // endfor k

    if (ColNode == NULL) {
      snprintf(g->Message, sizeof(g->Message), MSG(COL_ALLOC_ERR));
			throw (int)TYPE_AM_XML;
		} // endif ColNode

    } // endif ColNode

  if (Type == 1) {
    if (ValNode == NULL) {
      if (done < 2)
        ColNode->AddText(g, "\n\t\t");

      ValNode = ColNode->AddChildNode(g, Xname, Vxnp);
      } // endif ValNode

  } else // (Type == 0)
    if (AttNode == NULL)
      AttNode = ColNode->AddProperty(g, Xname, Vxap);

  if (ValNode == NULL && AttNode == NULL) {
    snprintf(g->Message, sizeof(g->Message), MSG(VAL_ALLOC_ERR));
    throw (int)TYPE_AM_XML;
    } // endif ValNode

  /*********************************************************************/
  /*  Get the string representation of Value according to column type. */
  /*********************************************************************/
  p = Value->GetCharString(buf);

  if (strlen(p) > (unsigned)Long) {
    snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_LONG), p, Name, Long);
		throw (int)TYPE_AM_XML;
	} else
    strcpy(Valbuf, p);

  /*********************************************************************/
  /*  Updating must be done only when not in checking pass.            */
  /*********************************************************************/
 fin:
  if (Status) {
    if (Type) {
      ValNode->SetContent(g, Valbuf, Long);
    } else
      AttNode->SetText(g, Valbuf, Long);

    } // endif Status

  } // end of WriteColumn

// ------------------------ XMULCOL functions ---------------------------

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the column node    */
/*  from the corresponding table, extract from it the node text and    */
/*  convert it to the column type.                                     */
/***********************************************************************/
void XMULCOL::ReadColumn(PGLOBAL g)
  {
  char *p;
  int   i, len;
  bool  b = Tdbp->Xpand;

  if (Nx != Tdbp->Irow) {                     // New row
    Nl = Tdbp->RowNode->SelectNodes(g, Xname, Nl);

    if ((N = Nl->GetLength())) {
      *(p = Valbuf) = '\0';
      len = Long;

      if (N > Tdbp->Limit) {
        N = Tdbp->Limit;
        snprintf(g->Message, sizeof(g->Message), "Multiple values limited to %d", Tdbp->Limit);
        PushWarning(g, Tdbp);
        } // endif N

      for (i = 0; i < N; i++) {
        ValNode = Nl->GetItem(g, i, Vxnp);

        if (ValNode->GetType() != XML_ELEMENT_NODE &&
            ValNode->GetType() != XML_ATTRIBUTE_NODE) {
          snprintf(g->Message, sizeof(g->Message), MSG(BAD_VALNODE), ValNode->GetType(), Name);
					throw (int)TYPE_AM_XML;
				} // endif type

        // Get the Xname value from the XML file
        switch (ValNode->GetContent(g, p, (b ? Long : len))) {
          case RC_OK:
            break;
          case RC_INFO:
            PushWarning(g, Tdbp);
            break;
          default:
            throw (int)TYPE_AM_XML;
          } // endswitch

        if (!b) {
          // Concatenate all values
          if (N - i > 1)
            strncat(Valbuf, ", ", len - strlen(p));

          if ((len -= strlen(p)) <= 0)
            break;

          p += strlen(p);
        } else            // Xpand
          p += (Long + 1);

        } // endfor i

      Value->SetValue_psz(Valbuf);
    } else {
      if (Nullable)
        Value->SetNull(true);

      Value->Reset();              // Null value
    } // endif ValNode

  } else if (Sx == Tdbp->Nsub)
    return;                        // Same row
  else                             // Expanded value
    Value->SetValue_psz(Valbuf + (Tdbp->Nsub * (Long + 1)));

  Nx = Tdbp->Irow;
  Sx = Tdbp->Nsub;
  Tdbp->NextSame = (Tdbp->Xpand && N - Sx > 1);
  } // end of ReadColumn

/***********************************************************************/
/*  WriteColumn: what this routine does is to access the last line     */
/*  read from the corresponding table, and rewrite the field           */
/*  corresponding to this column from the column buffer and type.      */
/***********************************************************************/
void XMULCOL::WriteColumn(PGLOBAL g)
  {
  char  *p, buf[16];
  int    done = 0;
  int   i, n, len, k = 0;
  PXNODE TopNode = NULL;

  if (trace(1))
    htrc("XML WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
          Name, Tdbp->GetTdb_No(), ColUse, Status);

  /*********************************************************************/
  /*  Check whether this node must be written.                         */
  /*********************************************************************/
  if (Value != To_Val)
    Value->SetValue_pval(To_Val, false);    // Convert the updated value

  if (Value->IsNull())
    return;

  /*********************************************************************/
  /*  If a check pass was done while updating, all node contruction    */
  /*  has been already one.                                            */
  /*********************************************************************/
  if (Status && Tdbp->Checked) {
    assert (ColNode);
    assert ((Type ? (void *)ValNode : (void *)AttNode) != NULL);
    goto fin;
    } // endif Checked

  /*********************************************************************/
  /*  On Insert, a Row node must be created for each row;              */
  /*  For columns having an Xpath, the Clist must be updated.          */
  /*********************************************************************/
  if (Tdbp->CheckRow(g, Nod))
		throw (int)TYPE_AM_XML;

  /*********************************************************************/
  /*  Find the column and value nodes to update or insert.             */
  /*********************************************************************/
  if (Tdbp->Clist) {
    n =  Tdbp->Clist->GetLength();
    ColNode = NULL;
  } else {
    n = 1;
    ColNode = Tdbp->RowNode->Clone(g, ColNode);
  } // endif Clist

  ValNode = NULL;

  for (i = 0; i < n; i++) {
    if (Tdbp->Clist)
      ColNode = Tdbp->Clist->GetItem(g, i, Cxnp);

    /*******************************************************************/
    /*  Check whether an Xpath was provided to go to the column node.  */
    /*******************************************************************/
    for (k = 0; k < Nod; k++) {
      if (k == Inod) {
        // This is the multiple node
        Nlx = ColNode->SelectNodes(g, Nodes[k], Nlx);
        ColNode = Nlx->GetItem(g, Tdbp->Nsub, Cxnp);
      } else
        ColNode = ColNode->SelectSingleNode(g, Nodes[k], Cxnp);

      if (ColNode == NULL)
        break;

      TopNode = ColNode;
      } // endfor k

    if (ColNode)
    {
      if (Inod == Nod) {
        /***************************************************************/
        /*  The node value can be multiple.                            */
        /***************************************************************/
        assert (Type);

        // Get the value Node from the XML list
        Nlx = ColNode->SelectNodes(g, Xname, Nlx);
        len = Nlx->GetLength();

        if (len > 1 && !Tdbp->Xpand) {
          snprintf(g->Message, sizeof(g->Message), MSG(BAD_VAL_UPDATE), Name);
					throw (int)TYPE_AM_XML;
				} else
          ValNode = Nlx->GetItem(g, Tdbp->Nsub, Vxnp);

      } else  // Inod != Nod
      {
        if (Type)
          ValNode = ColNode->SelectSingleNode(g, Xname, Vxnp);
        else
          AttNode = ColNode->GetAttribute(g, Xname, Vxap);
      }
    }
    if (TopNode || ValNode || AttNode)
      break;                     // We found the good column
    else if (Tdbp->Clist)
      ColNode = NULL;

    } // endfor i

  /*********************************************************************/
  /*  Create missing nodes.                                            */
  /*********************************************************************/
  if (ColNode == NULL) {
    if (TopNode == NULL)
    {
      if (Tdbp->Clist) {
        Tdbp->RowNode->AddText(g, "\n\t\t");
        ColNode = Tdbp->RowNode->AddChildNode(g, Tdbp->Colname);
        done = 2;
        TopNode = ColNode;
      } else
        TopNode = Tdbp->RowNode;
    }

    for (; k < Nod && TopNode; k++) {
      if (!done) {
        TopNode->AddText(g, "\n\t\t");
        done = 1;
        } // endif done

      ColNode = TopNode->AddChildNode(g, Nodes[k], Cxnp);
      TopNode = ColNode;
      } // endfor k

    if (ColNode == NULL) {
      snprintf(g->Message, sizeof(g->Message), MSG(COL_ALLOC_ERR));
			throw (int)TYPE_AM_XML;
		} // endif ColNode

    } // endif ColNode

  if (Type == 1) {
    if (ValNode == NULL) {
      if (done < 2)
        ColNode->AddText(g, "\n\t\t");

      ValNode = ColNode->AddChildNode(g, Xname, Vxnp);
      } // endif ValNode

  } else // (Type == 0)
    if (AttNode == NULL)
      AttNode = ColNode->AddProperty(g, Xname, Vxap);

  if (ValNode == NULL && AttNode == NULL) {
    snprintf(g->Message, sizeof(g->Message), MSG(VAL_ALLOC_ERR));
		throw (int)TYPE_AM_XML;
	} // endif ValNode

  /*********************************************************************/
  /*  Get the string representation of Value according to column type. */
  /*********************************************************************/
  p = Value->GetCharString(buf);

  if (strlen(p) > (unsigned)Long) {
    snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_LONG), p, Name, Long);
		throw (int)TYPE_AM_XML;
	} else
    strcpy(Valbuf, p);

  /*********************************************************************/
  /*  Updating must be done only when not in checking pass.            */
  /*********************************************************************/
 fin:
  if (Status) {
    if (Type) {
      ValNode->SetContent(g, Valbuf, Long);
    } else
      AttNode->SetText(g, Valbuf, Long);

    } // endif Status

  } // end of WriteColumn

/* ------------------------ XPOSCOL functions ------------------------ */

/***********************************************************************/
/*  ReadColumn: what this routine does is to access the column node    */
/*  from the corresponding table, extract from it the node text and    */
/*  convert it to the column type.                                     */
/***********************************************************************/
void XPOSCOL::ReadColumn(PGLOBAL g)
  {
  if (Nx == Tdbp->Irow)
    return;                         // Same row than the last read

  if (Tdbp->Clist == NULL) {
    snprintf(g->Message, sizeof(g->Message), MSG(MIS_TAG_LIST));
		throw (int)TYPE_AM_XML;
	} // endif Clist

  if ((ValNode = Tdbp->Clist->GetItem(g, Rank, Vxnp))) {
    // Get the column value from the XML file
    switch (ValNode->GetContent(g, Valbuf, Long + 1)) {
      case RC_OK:
        break;
      case RC_INFO:
        PushWarning(g, Tdbp);
        break;
      default:
				throw (int)TYPE_AM_XML;
		} // endswitch

    Value->SetValue_psz(Valbuf);
  } else {
    if (Nullable)
      Value->SetNull(true);

    Value->Reset();              // Null value
  } // endif ValNode

  Nx = Tdbp->Irow;
  } // end of ReadColumn

/***********************************************************************/
/*  WriteColumn: what this routine does is to access the last line     */
/*  read from the corresponding table, and rewrite the field           */
/*  corresponding to this column from the column buffer and type.      */
/***********************************************************************/
void XPOSCOL::WriteColumn(PGLOBAL g)
  {
  char          *p, buf[16];
  int           i, k, n;

  if (trace(1))
    htrc("XML WriteColumn: col %s R%d coluse=%.4X status=%.4X\n",
          Name, Tdbp->GetTdb_No(), ColUse, Status);

  /*********************************************************************/
  /*  Check whether this node must be written.                         */
  /*********************************************************************/
  if (Value != To_Val)
    Value->SetValue_pval(To_Val, false);    // Convert the updated value

  if (Value->IsNull())
    return;

  /*********************************************************************/
  /*  If a check pass was done while updating, all node contruction    */
  /*  has been already one.                                            */
  /*********************************************************************/
  if (Status && Tdbp->Checked) {
    assert (ValNode);
    goto fin;
    } // endif Checked

  /*********************************************************************/
  /*  On Insert, a Row node must be created for each row;              */
  /*  For all columns the Clist must be updated.                       */
  /*********************************************************************/
  if (Tdbp->CheckRow(g, true))
		throw (int)TYPE_AM_XML;

  /*********************************************************************/
  /*  Find the column and value nodes to update or insert.             */
  /*********************************************************************/
  if (Tdbp->Clist == NULL) {
    snprintf(g->Message, sizeof(g->Message), MSG(MIS_TAG_LIST));
		throw (int)TYPE_AM_XML;
	} // endif Clist

  n =  Tdbp->Clist->GetLength();
  k = Rank;

  if (!(ValNode = Tdbp->Clist->GetItem(g, k, Vxnp))) {
    /*******************************************************************/
    /*  Create missing column nodes.                                   */
    /*******************************************************************/
    Tdbp->RowNode->AddText(g, "\n\t\t");

    for (i = n; i <= k; i++)
      ValNode = Tdbp->RowNode->AddChildNode(g, Tdbp->Colname, Vxnp);

    assert (ValNode);
    } // endif ValNode

  /*********************************************************************/
  /*  Get the string representation of Value according to column type. */
  /*********************************************************************/
  p = Value->GetCharString(buf);

  if (strlen(p) > (unsigned)Long) {
    snprintf(g->Message, sizeof(g->Message), MSG(VALUE_TOO_LONG), p, Name, Long);
		throw (int)TYPE_AM_XML;
	} else
    strcpy(Valbuf, p);

  /*********************************************************************/
  /*  Updating must be done only when not in checking pass.            */
  /*********************************************************************/
 fin:
  if (Status)
    ValNode->SetContent(g, Valbuf, Long);

  } // end of WriteColumn

/* ---------------------------TDBXCT class --------------------------- */

/***********************************************************************/
/*  TDBXCT class constructor.                                          */
/***********************************************************************/
TDBXCT::TDBXCT(PXMLDEF tdp) : TDBCAT(tdp)
  {
  Topt = tdp->GetTopt();
  //Db = (char*)tdp->GetDB();
	Db = (char*)tdp->Schema;
	Tabn = tdp->Tabname;
  } // end of TDBXCT constructor

/***********************************************************************/
/*  GetResult: Get the list the JSON file columns.                     */
/***********************************************************************/
PQRYRES TDBXCT::GetResult(PGLOBAL g)
  {
  return XMLColumns(g, Db, Tabn, Topt, false);
  } // end of GetResult

/* ------------------------ End of Tabxml ---------------------------- */