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

/***********************************************************************/
/*  Include required compiler header files.                            */
/***********************************************************************/
#include <stdio.h>
#include <fcntl.h>
#include <errno.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 "my_global.h"

#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 "xindex.h"
#include "plgxml.h"
#include "tabxml.h"

extern "C" {
extern char version[];
extern int  trace;
} // "C"

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

/* -------------- 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;
  Coltype = 1;
  Limit = 0;
  Header = 0;
  Xpand = false;
  Usedom = false;
  } // end of XMLDEF constructor

/***********************************************************************/
/*  DefineAM: define specific AM block values from XDB file.           */
/***********************************************************************/
bool XMLDEF::DefineAM(PGLOBAL g, LPCSTR am, int poff)
  {
  char  *defrow, *defcol, buf[10];
//void  *memp = Cat->GetDescp();
//PSZ    dbfile = Cat->GetDescFile();

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

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

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

  defrow = defcol = "";
  Cat->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:
      sprintf(g->Message, MSG(INV_COL_TYPE), buf);
      return true;
    } // endswitch typname

  Tabname = Cat->GetStringCatInfo(g, "Name", Name);  // Deprecated
  Tabname = Cat->GetStringCatInfo(g, "Table_name", Tabname);
  Rowname = Cat->GetStringCatInfo(g, "Rownode", defrow);
  Colname = Cat->GetStringCatInfo(g, "Colnode", defcol);
  Mulnode = Cat->GetStringCatInfo(g, "Mulnode", "");
  XmlDB = Cat->GetStringCatInfo(g, "XmlDB", "");
  Nslist = Cat->GetStringCatInfo(g, "Nslist", "");
  DefNs = Cat->GetStringCatInfo(g, "DefNs", "");
  Limit = Cat->GetIntCatInfo("Limit", 2);
  Xpand = (Cat->GetIntCatInfo("Expand", 0) != 0);
  Header = Cat->GetIntCatInfo("Header", 0);
  Cat->GetCharCatInfo("Xmlsup", "*", buf, sizeof(buf));

//if (*buf == '*')           // Try the old (deprecated) option
//  Cat->GetCharCatInfo("Method", "*", buf, sizeof(buf));

//if (*buf == '*')           // Is there a default for the database?
//  Cat->GetCharCatInfo("Defxml", 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 = Cat->GetStringCatInfo(g, "Attribute", "");
  Hdattr = Cat->GetStringCatInfo(g, "HeadAttr", "");

  return false;
  } // end of DefineAM

/***********************************************************************/
/*  GetTable: makes a new TDB of the proper type.                      */
/***********************************************************************/
PTDB XMLDEF::GetTable(PGLOBAL g, MODE m)
  {
  return new(g) TDBXML(this);
  } // end of GetTable

/***********************************************************************/
/*  DeleteTableFile: Delete XML table files using platform API.        */
/***********************************************************************/
bool XMLDEF::DeleteTableFile(PGLOBAL g)
  {
  char    filename[_MAX_PATH];
  bool    rc;

  // Delete the XML table file if not protected
  if (!IsReadOnly()) {
    PlugSetPath(filename, Fn, GetPath());
#if defined(WIN32)
    rc = !DeleteFile(filename);
#else    // UNIX
    rc = remove(filename);
#endif   // UNIX
  } else
    rc =true;

  return rc;                                  // Return true if error
  } // end of DeleteTableFile

/* ------------------------- 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;
  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;
  Coltype = tdp->Coltype;
  Limit = tdp->Limit;
  Xpand = tdp->Xpand;
  Changed = false;
  Checked = false;
  NextSame = false;
  NewRow = false;
  Hasnod = false;
  Write = false;
  Bufdone = false;
  Nodedone = false;
  Void = false;
  Usedom = tdp->Usedom;
  Header = tdp->Header;
  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;
  Coltype = tdbp->Coltype;
  Limit = tdbp->Limit;
  Xpand = tdbp->Xpand;
  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;
  Nrow = tdbp->Nrow;
  Irow = tdbp->Irow;
  Nsub = tdbp->Nsub;
  N = tdbp->N;
  } // end of TDBXML copy constructor

// Used for update
PTDB TDBXML::CopyOne(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 CopyOne

/***********************************************************************/
/*  Allocate XML column description block.                             */
/***********************************************************************/
PCOL TDBXML::MakeCol(PGLOBAL g, PCOLDEF cdp, PCOL cprec, int n)
  {
  if (trace)
    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(PGLOBAL g, 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)
    htrc("TDBXML: loading %s\n", filename);

  /*********************************************************************/
  /*  Firstly we check whether this file have been already loaded.     */
  /*********************************************************************/
  if (Mode == MODE_READ)
    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)) {
      sprintf(g->Message, MSG(INIT_FAILED), (Usedom) ? "DOM" : "libxml2");
      return RC_FX;
      } // endif init

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

    // Parse the XML file
    if (Docp->ParseFile(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 && !Bufdone) {
    // 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 (colp->AllocBuf(g, Mode == MODE_INSERT))
          return true;

    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
        strcpy(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, '/'))
        strcat(strcpy(tabpath, "//"), Tabname);
      else
        strcpy(tabpath, Tabname);

      // Evaluate table xpath
      if ((TabNode = Root->SelectSingleNode(g, tabpath))) {
        if (TabNode->GetType() != XML_ELEMENT_NODE) {
          sprintf(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
          sprintf(g->Message, MSG(MISSING_NODE), XmlDB, Xfile);
          goto error;
          } // endif DBnode

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

        DBnode->AddText(g, "\n");
      } else
        TabNode = Root;              // Try this ?

    } 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")) {
          strcpy(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
//      sprintf(buf, MSG(CREATED_PLUGDB), version);
        sprintf(buf, " Created by CONNECT %s ", version);
        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) {
          strcpy(g->Message, MSG(XML_INIT_ERROR));
          goto error;
        } else if (SetTabNode(g))
          goto error;

      } else {
        sprintf(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
      sprintf(g->Message, MSG(LOADING_FAILED), Xfile);
      goto error;
    } else // (rc == RC_FX)
      goto error;

    // Get row node list
    if (Rowname)
      Nlist = TabNode->SelectNodes(g, Rowname);
    else
      Nlist = TabNode->GetChildElements(g);

    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)
      sprintf(g->Message, "%s: %s", MSG(COM_ERROR), buf);
    else
      sprintf(g->Message, "%s hr=%p", MSG(COM_ERROR), e.Error());

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

  if (Root && Columns && !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 {
      strcpy(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 (Xpand || Coltype == 2) ? 0 : 1;

  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)
    MaxSize = Cardinality(g) * ((Xpand) ? Limit : 1);

  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.      */
    /*******************************************************************/
    sprintf(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)
      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 > 1)
      htrc("TDBXML ReadDB: Irow=%d RowNode=%p\n", Irow, RowNode);

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

    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 {
      strcpy(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)
  {
  if (irc == RC_FX) {
    // Delete all rows
    for (Irow = 0; Irow < Nrow; Irow++)
      if ((RowNode = Nlist->GetItem(g, Irow, RowNode)) == NULL) {
        sprintf(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];
//    PDBUSER dup = (PDBUSER)g->Activityp->Aptr;

      // 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
//      longjmp(g->jumper[g->jump_level], TYPE_AM_XML);
        } // endif DumpDoc
      
      } // endif Changed

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

  } // end of CloseDB

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

/***********************************************************************/
/*  XMLCOL public constructor.                                        */
/***********************************************************************/
XMLCOL::XMLCOL(PCOLDEF cdp, PTDB tdbp, PCOL cprec, int i, PSZ 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;
  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;
  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

  Valbuf = (char*)PlugSubAlloc(g, NULL, Long + 1);
  Valbuf[Long] = '\0';
  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, 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) {
      sprintf(pbuf, Tdbp->Colname, Rank + ((Tdbp->Usedom) ? 0 : 1));
      strcat(pbuf, "/");
      } // endif Colname

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

    if (trace)
      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) {
          strcpy(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)) {
          sprintf(g->Message, MSG(XPATH_NOT_SUPP), Name);
          return true;
        } else
          Nodes[i] = p;

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

      } // endfor i, p

    if (*p == '/' || *p == '.') {
      sprintf(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
    Tdbp->Hasnod = true;
    return false;
  } 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
    new(this) XMULCOL(Value);       // Change the class of this column
    } // endif Inod

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

  if (trace)
    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)) {
    sprintf(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_FLOAT)
      // Float values must be written with the correct (column) precision
      // Note: maybe this should be forced by ShowValue instead of this ?
      value->SetPrec(GetPrecision());

    Value = value;            // Directly access the external value
  } else {
    // Values are not of the (good) column type
    if (check) {
      sprintf(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) {
      sprintf(g->Message, MSG(BAD_VALNODE), ValNode->GetType(), Name);
      longjmp(g->jumper[g->jump_level], 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:
        longjmp(g->jumper[g->jump_level], 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;
//PXATTR AttNode = NULL;

  if (trace)
    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 != 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))
    longjmp(g->jumper[g->jump_level], 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 ((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;

    } // 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) {
      strcpy(g->Message, MSG(COL_ALLOC_ERR));
      longjmp(g->jumper[g->jump_level], 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) {
    strcpy(g->Message, MSG(VAL_ALLOC_ERR));
    longjmp(g->jumper[g->jump_level], TYPE_AM_XML);
    } // endif ValNode

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

  if (strlen(p) > (unsigned)Long) {
    sprintf(g->Message, MSG(VALUE_TOO_LONG), p, Name, Long);
    longjmp(g->jumper[g->jump_level], 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, n, len;

  if (Nx != Tdbp->Irow)                     // New row
    Nl = Tdbp->RowNode->SelectNodes(g, Xname, Nl);
  else if (Sx == Tdbp->Nsub)
    return;                                 // Same row

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

    for (i = Tdbp->Nsub; i < n; i++) {
      ValNode = Nl->GetItem(g, i, Vxnp);

      if (ValNode->GetType() != XML_ELEMENT_NODE &&
          ValNode->GetType() != XML_ATTRIBUTE_NODE) {
        sprintf(g->Message, MSG(BAD_VALNODE), ValNode->GetType(), Name);
        longjmp(g->jumper[g->jump_level], TYPE_AM_XML);
        } // endif type

      // Get the Xname value from the XML file
      switch (ValNode->GetContent(g, p, len + 1)) {
        case RC_OK:
          break;
        case RC_INFO:
          PushWarning(g, Tdbp);
          break;
        default:
          longjmp(g->jumper[g->jump_level], TYPE_AM_XML);
        } // endswitch

      if (!Tdbp->Xpand) {
        // Concatenate all values
        if (n - i > 1)
          strncat(Valbuf, ", ", Long + 1);

        len -= strlen(p);
        p += strlen(p);
      } else
        break;

      } // endfor i

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

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

  Nx = Tdbp->Irow;
  Sx = Tdbp->Nsub;
  Tdbp->NextSame = (Tdbp->Xpand && Nl->GetLength() - 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;
//PXATTR AttNode = NULL;

  if (trace)
    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))
    longjmp(g->jumper[g->jump_level], 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) {
          sprintf(g->Message, MSG(BAD_VAL_UPDATE), Name);
          longjmp(g->jumper[g->jump_level], 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) {
      strcpy(g->Message, MSG(COL_ALLOC_ERR));
      longjmp(g->jumper[g->jump_level], 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) {
    strcpy(g->Message, MSG(VAL_ALLOC_ERR));
    longjmp(g->jumper[g->jump_level], TYPE_AM_XML);
    } // endif ValNode

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

  if (strlen(p) > (unsigned)Long) {
    sprintf(g->Message, MSG(VALUE_TOO_LONG), p, Name, Long);
    longjmp(g->jumper[g->jump_level], 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) {
    strcpy(g->Message, MSG(MIS_TAG_LIST));
    longjmp(g->jumper[g->jump_level], 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:
        longjmp(g->jumper[g->jump_level], 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)
    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))
    longjmp(g->jumper[g->jump_level], TYPE_AM_XML);

  /*********************************************************************/
  /*  Find the column and value nodes to update or insert.             */
  /*********************************************************************/
  if (Tdbp->Clist == NULL) {
    strcpy(g->Message, MSG(MIS_TAG_LIST));
    longjmp(g->jumper[g->jump_level], 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) {
    sprintf(g->Message, MSG(VALUE_TOO_LONG), p, Name, Long);
    longjmp(g->jumper[g->jump_level], 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

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