/******************************************************************/
/*  Implementation of XML document processing using libxml2       */
/*  Author: Olivier Bertrand                2007-2016             */
/******************************************************************/
#include "my_global.h"
#include <string.h>
#include <stdio.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/catalog.h>
#include <libxml/xmlschemastypes.h>
#include <libxml/relaxng.h>

#if !defined(LIBXML_TREE_ENABLED) || !defined(LIBXML_OUTPUT_ENABLED)
#error "tree support not compiled in"
#endif

#if !defined(LIBXML_XPATH_ENABLED) || !defined(LIBXML_SAX1_ENABLED)
#error "XPath not supported"
#endif

#include "global.h"
#include "plgdbsem.h"
#include "xobject.h"
#include "libdoc.h"

#include "sql_string.h"

/******************************************************************/
/*  Declaration of XML document processing using libxml2          */
/*  Author: Olivier Bertrand                2007-2012             */
/******************************************************************/
#include "plgxml.h"

typedef class LIBXMLDOC    *PXDOC2;
typedef class XML2NODE     *PNODE2;
typedef class XML2ATTR     *PATTR2;
typedef class XML2NODELIST *PLIST2;

/******************************************************************/
/* XML2 block. Must have the same layout than FBLOCK up to Type.  */
/******************************************************************/
typedef struct _x2block {         /* Loaded XML file block        */
  struct _x2block   *Next;
  LPCSTR             Fname;       /* Point on file name           */
  size_t             Length;      /* Used to tell if read mode    */
  short              Count;       /* Nb of times file is used     */
  short              Type;        /* TYPE_FB_XML                  */
  int                Retcode;     /* Return code from Load        */
  xmlDocPtr          Docp;        /* Document interface pointer   */
  } X2BLOCK, *PX2BLOCK;

/******************************************************************/
/*  Declaration of libxml2 document.                              */
/******************************************************************/
class LIBXMLDOC : public XMLDOCUMENT {
  friend class XML2NODE;
  friend class XML2ATTR;
 public:
  // Constructor
  LIBXMLDOC(char *nsl, char *nsdf, char *enc, PFBLOCK fp);

  // Properties
  short  GetDocType(void) override {return TYPE_FB_XML2;}
  void  *GetDocPtr(void) override {return Docp;}
  void   SetNofree(bool b) override {Nofreelist = b;}

  // Methods
	bool    Initialize(PGLOBAL g, PCSZ entry, bool zipped) override;
  bool    ParseFile(PGLOBAL g, char *fn) override;
  bool    NewDoc(PGLOBAL g, PCSZ ver) override;
  void    AddComment(PGLOBAL g, char *com) override;
  PXNODE  GetRoot(PGLOBAL g) override;
  PXNODE  NewRoot(PGLOBAL g, char *name) override;
  PXNODE  NewPnode(PGLOBAL g, char *name) override;
  PXATTR  NewPattr(PGLOBAL g) override;
  PXLIST  NewPlist(PGLOBAL g) override;
  int     DumpDoc(PGLOBAL g, char *ofn) override;
  void    CloseDoc(PGLOBAL g, PFBLOCK xp) override;
  PFBLOCK LinkXblock(PGLOBAL g, MODE m, int rc, char *fn) override;

 protected:
//        bool     CheckDocument(FILE *of, xmlNodePtr np);
          xmlNodeSetPtr GetNodeList(PGLOBAL g, xmlNodePtr np, char *xp);
          int      Decode(xmlChar *cnt, char *buf, int n);
          xmlChar *Encode(PGLOBAL g, char *txt);

  // Members
  xmlDocPtr          Docp;
  xmlNodeSetPtr      Nlist;
  xmlXPathContextPtr Ctxp;
  xmlXPathObjectPtr  Xop;
  xmlXPathObjectPtr  NlXop;
  char              *Buf;                  // Temporary
  bool               Nofreelist;
}; // end of class LIBXMLDOC

/******************************************************************/
/*  Declaration of libxml2 node.                                  */
/******************************************************************/
class XML2NODE : public XMLNODE {
  friend class LIBXMLDOC;
  friend class XML2NODELIST;
 public:
  // Properties
  char  *GetName(PGLOBAL g) override {return (char*)Nodep->name;}
  int    GetType(void) override;
  PXNODE GetNext(PGLOBAL g) override;
  PXNODE GetChild(PGLOBAL g) override;

  // Methods
  RCODE  GetContent(PGLOBAL g, char *buf, int len) override;
  bool   SetContent(PGLOBAL g, char *txtp, int len) override;
  PXNODE Clone(PGLOBAL g, PXNODE np) override;
  PXLIST GetChildElements(PGLOBAL g, char *xp, PXLIST lp) override;
  PXLIST SelectNodes(PGLOBAL g, char *xp, PXLIST lp) override;
  PXNODE SelectSingleNode(PGLOBAL g, char *xp, PXNODE np) override;
  PXATTR GetAttribute(PGLOBAL g, char *name, PXATTR ap) override;
  PXNODE AddChildNode(PGLOBAL g, PCSZ name, PXNODE np) override;
  PXATTR AddProperty(PGLOBAL g, char *name, PXATTR ap) override;
  void   AddText(PGLOBAL g, PCSZ txtp) override;
  void   DeleteChild(PGLOBAL g, PXNODE dnp) override;

 protected:
  // Constructor
  XML2NODE(PXDOC dp, xmlNodePtr np);

  // Members
  xmlDocPtr  Docp;
  xmlChar   *Content;
  xmlNodePtr Nodep;
}; // end of class XML2NODE

/******************************************************************/
/*  Declaration of libxml2 node list.                             */
/******************************************************************/
class XML2NODELIST : public XMLNODELIST {
  friend class LIBXMLDOC;
  friend class XML2NODE;
 public:
  // Methods
  int    GetLength(void) override;
  PXNODE GetItem(PGLOBAL g, int n, PXNODE np) override;
  bool   DropItem(PGLOBAL g, int n) override;

 protected:
  // Constructor
  XML2NODELIST(PXDOC dp, xmlNodeSetPtr lp);

  // Members
  xmlNodeSetPtr Listp;
}; // end of class XML2NODELIST

/******************************************************************/
/*  Declaration of libxml2 attribute.                             */
/******************************************************************/
class XML2ATTR : public XMLATTRIBUTE {
  friend class LIBXMLDOC;
  friend class XML2NODE;
 public:
  // Properties
  char  *GetName(PGLOBAL g) override {return (char*)Atrp->name;}
  PXATTR GetNext(PGLOBAL g) override;

  // Methods
  RCODE  GetText(PGLOBAL g, char *bufp, int len) override;
  bool   SetText(PGLOBAL g, char *txtp, int len) override;

 protected:
  // Constructor
  XML2ATTR(PXDOC dp, xmlAttrPtr ap, xmlNodePtr np);

  // Members
  xmlAttrPtr Atrp;
  xmlNodePtr Parent;
}; // end of class XML2ATTR



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

#if defined(MEMORY_TRACE)
static int  m = 0;
static char s[500];
/**************************************************************************/
/*  Tracing output function.                                              */
/**************************************************************************/
void xtrc(char const *fmt, ...)
  {
  va_list ap;
  va_start (ap, fmt);
																							;
//vfprintf(stderr, fmt, ap);
  vsnprintf(s, sizeof(s), fmt, ap);
  if (s[strlen(s)-1] == '\n')
      s[strlen(s)-1] = 0;
  va_end (ap);
  } // end of htrc

static xmlFreeFunc Free; 
static xmlMallocFunc Malloc;
static xmlMallocFunc MallocA; 
static xmlReallocFunc Realloc; 
static xmlStrdupFunc Strdup;

void xmlMyFree(void *mem)
{
  if (trace(1)) { 
    htrc("%.4d Freeing          at %p   %-.256s\n", ++m, mem, s);
    *s = 0;
    } // endif trace
  Free(mem);
} // end of xmlMyFree

void *xmlMyMalloc(size_t size)
{
  void *p = Malloc(size);
  if (trace(1)) { 
    htrc("%.4d Allocating %.5d at %p   %-.256s\n", ++m, size, p, s);
    *s = 0;
    } // endif trace
  return p;
} // end of xmlMyMalloc

void *xmlMyMallocAtomic(size_t size)
{
  void *p = MallocA(size);
  if (trace(1)) { 
    htrc("%.4d Atom alloc %.5d at %p   %-.256s\n", ++m, size, p, s);
    *s = 0;
    } // endif trace
  return p;
} // end of xmlMyMallocAtomic

void *xmlMyRealloc(void *mem, size_t size)
{
  void *p = Realloc(mem, size);
  if (trace(1)) { 
    htrc("%.4d ReAlloc    %.5d to %p from %p   %-.256s\n", ++m, size, p, mem, s);
    *s = 0;
    } // endif trace
  return p;
} // end of xmlMyRealloc

char *xmlMyStrdup(const char *str)
{
  char *p = Strdup(str);
  if (trace(1)) { 
    htrc("%.4d Duplicating      to %p from %p %-.256s   %-.256s\n", ++m, p, str, str, s);
    *s = 0;
    } // endif trace
  return p;
} // end of xmlMyStrdup
#define htrc xtrc
#endif   // MEMORY_TRACE

/******************************************************************/
/*  Return a LIBXMLDOC as a XMLDOC.                               */
/******************************************************************/
PXDOC GetLibxmlDoc(PGLOBAL g, char *nsl, char *nsdf, 
                                         char *enc, PFBLOCK fp)
  {
  return (PXDOC) new(g) LIBXMLDOC(nsl, nsdf, enc, fp);
  } // end of GetLibxmlDoc

/******************************************************************/
/*  XML library initialization function.                          */
/******************************************************************/
void XmlInitParserLib(void)
  {
#if defined(MEMORY_TRACE)
int	rc = xmlGcMemGet(&Free, &Malloc, &MallocA, &Realloc, &Strdup);

if (!rc)
  rc = xmlGcMemSetup(xmlMyFree, 
                     xmlMyMalloc, 
                     xmlMyMallocAtomic,
                     xmlMyRealloc,
                     xmlMyStrdup); 

#endif   // MEMORY_TRACE
  xmlInitParser();
  } // end of XmlInitParserLib

/******************************************************************/
/*  XML library cleanup function.                                 */
/******************************************************************/
void XmlCleanupParserLib(void)
  {
  } // end of XmlCleanupParserLib

/******************************************************************/
/*  Close a loaded libxml2 XML file.                              */
/******************************************************************/
void CloseXML2File(PGLOBAL g, PFBLOCK fp, bool all)
  {
  PX2BLOCK xp = (PX2BLOCK)fp;

  if (trace(1))
    htrc("CloseXML2File: xp=%p count=%d\n", xp, (xp) ? xp->Count : 0);

  if (xp && xp->Count > 1 && !all) {
    xp->Count--;
  } else if (xp && xp->Count > 0) {
    xmlFreeDoc(xp->Docp);
    xp->Count = 0;
  } // endif

  } // end of CloseXML2File

/* ---------------------- class LIBXMLDOC ----------------------- */

/******************************************************************/
/*  LIBXMLDOC constructor.                                        */
/******************************************************************/
LIBXMLDOC::LIBXMLDOC(char *nsl, char *nsdf, char *enc, PFBLOCK fp)
         : XMLDOCUMENT(nsl, nsdf, enc)
  {
  assert (!fp || fp->Type == TYPE_FB_XML2);
  Docp = (fp) ? ((PX2BLOCK)fp)->Docp : NULL;
  Nlist = NULL;
  Ctxp = NULL;
  Xop = NULL;
  NlXop = NULL;
  Buf = NULL;
  Nofreelist = false;
  } // end of LIBXMLDOC constructor

/******************************************************************/
/*  Initialize XML parser and check library compatibility.        */
/******************************************************************/
bool LIBXMLDOC::Initialize(PGLOBAL g, PCSZ entry, bool zipped)
{
	if (zipped && InitZip(g, entry))
		return true;

  xmlKeepBlanksDefault(1);
  return MakeNSlist(g);
} // end of Initialize

/******************************************************************/
/* Parse the XML file and construct node tree in memory.          */
/******************************************************************/
bool LIBXMLDOC::ParseFile(PGLOBAL g, char *fn)
  {
  if (trace(1))
    htrc("ParseFile\n");

	if (zip) {
		// Parse an in memory document
		char *xdoc = GetMemDoc(g, fn);

		Docp = (xdoc) ? xmlParseDoc((const xmlChar *)xdoc) : NULL;
	} else
		Docp = xmlParseFile(fn);

	if (Docp) {
		if (Docp->encoding)
      Encoding = (char*)Docp->encoding;

    return false;
  } else if (xmlGetLastError())
    xmlResetLastError();

  return true;
  } // end of ParseFile

/******************************************************************/
/* Create or reuse an Xblock for this document.                   */
/******************************************************************/
PFBLOCK LIBXMLDOC::LinkXblock(PGLOBAL g, MODE m, int rc, char *fn)
  {
  PDBUSER  dup = (PDBUSER)g->Activityp->Aptr;
  PX2BLOCK xp = (PX2BLOCK)PlugSubAlloc(g, NULL, sizeof(X2BLOCK));

  memset(xp, 0, sizeof(X2BLOCK));
  xp->Next = (PX2BLOCK)dup->Openlist;
  dup->Openlist = (PFBLOCK)xp;
  xp->Type = TYPE_FB_XML2;
  xp->Fname = (LPCSTR)PlugDup(g, fn);
  xp->Count = 1;
  xp->Length = (m == MODE_READ) ? 1 : 0;
  xp->Retcode = rc;
  xp->Docp = Docp;

  // Return xp as a fp
  return (PFBLOCK)xp;
  } // end of LinkXblock

/******************************************************************/
/* Construct and add the XML processing instruction node.         */
/******************************************************************/
bool LIBXMLDOC::NewDoc(PGLOBAL g, PCSZ ver)
  {
  if (trace(1))
    htrc("NewDoc\n");

  return ((Docp = xmlNewDoc(BAD_CAST ver)) == NULL);
  } // end of NewDoc

/******************************************************************/
/*  Add a new comment node to the document.                       */
/******************************************************************/
void LIBXMLDOC::AddComment(PGLOBAL g, char *txtp)
  {
  if (trace(1))
    htrc("AddComment: %-.256s\n", txtp);

  xmlNodePtr cp = xmlNewDocComment(Docp, BAD_CAST txtp);
  xmlAddChild((xmlNodePtr)Docp, cp);
  } // end of AddText

/******************************************************************/
/* Return the node class of the root of the document.             */
/******************************************************************/
PXNODE LIBXMLDOC::GetRoot(PGLOBAL g)
  {
  if (trace(1))
    htrc("GetRoot\n");

  xmlNodePtr root = xmlDocGetRootElement(Docp);

  if (!root)
    return NULL;

  return new(g) XML2NODE(this, root);
  } // end of GetRoot

/******************************************************************/
/* Create a new root element and return its class node.           */
/******************************************************************/
PXNODE LIBXMLDOC::NewRoot(PGLOBAL g, char *name)
  {
  if (trace(1))
    htrc("NewRoot: %-.256s\n", name);

  xmlNodePtr root = xmlNewDocNode(Docp, NULL, BAD_CAST name, NULL);

  if (root) {
    xmlDocSetRootElement(Docp, root);
    return new(g) XML2NODE(this, root);
  } else
    return NULL;

  } // end of NewRoot

/******************************************************************/
/* Return a void XML2NODE node class.                             */
/******************************************************************/
PXNODE LIBXMLDOC::NewPnode(PGLOBAL g, char *name)
  {
  if (trace(1))
    htrc("NewNode: %-.256s\n", name);

  xmlNodePtr nop;

  if (name) {
    nop = xmlNewDocNode(Docp, NULL, BAD_CAST name, NULL);

    if (nop == NULL)
      return NULL;

  } else
    nop = NULL;

  return new(g) XML2NODE(this, nop);
  } // end of NewPnode

/******************************************************************/
/* Return a void XML2ATTR node class.                             */
/******************************************************************/
PXATTR LIBXMLDOC::NewPattr(PGLOBAL g)
  {
  return new(g) XML2ATTR(this, NULL, NULL);
  } // end of NewPattr

/******************************************************************/
/* Return a void XML2ATTR node class.                             */
/******************************************************************/
PXLIST LIBXMLDOC::NewPlist(PGLOBAL g)
  {
  return new(g) XML2NODELIST(this, NULL);
  } // end of NewPlist

/******************************************************************/
/* Dump the node tree to a new XML file.                          */
/******************************************************************/
int LIBXMLDOC::DumpDoc(PGLOBAL g, char *ofn)
  {
  int   rc = 0;
  FILE *of;

  if (trace(1))
    htrc("DumpDoc: %-.256s\n", ofn);

  if (!(of= global_fopen(g, MSGID_CANNOT_OPEN, ofn, "w")))
    return -1;

#if 1
  // This function does not crash (
  if (xmlSaveFormatFileEnc((const char *)ofn, Docp, Encoding, 0) < 0) {
    const xmlError *err = xmlGetLastError();
    strcpy(g->Message, (err) ? err->message : "Error saving XML doc");
    xmlResetLastError();
    rc = -1;
    } // endif Save
//  rc = xmlDocDump(of, Docp);
#else // 0
  // Until this function is fixed, do the job ourself
  xmlNodePtr Rootp;

  // Save the modified document
  fprintf(of, "<?xml version=\"1.0\" encoding=\"%-.256s\"?>\n", Encoding);
  fprintf(of, "<!-- Created by CONNECT %-.256s -->\n", version);

  if (!(Rootp = xmlDocGetRootElement(Docp)))
    return 1;

  Buf = (char*)PlugSubAlloc(g, NULL, 1024);
  rc = iconv_close(Cd2);
  Cd2 = iconv_open(Encoding, "UTF-8");
  rc = CheckDocument(of, Rootp);
#endif // 0

  fclose(of);
  return rc;
  } // end of Dump

/******************************************************************/
/* Free the document, cleanup the XML library, and                */
/* debug memory for regression tests.                             */
/******************************************************************/
void LIBXMLDOC::CloseDoc(PGLOBAL g, PFBLOCK xp)
  {
  if (trace(1))
    htrc("CloseDoc: xp=%p count=%d\n", xp, (xp) ? xp->Count : 0);

//if (xp && xp->Count == 1) {
  if (xp) {
    if (Nlist) {
      xmlXPathFreeNodeSet(Nlist);

      if (xmlGetLastError())
        xmlResetLastError();

      Nlist = NULL;
      } // endif Nlist

    if (Xop) {
      xmlXPathFreeObject(Xop);

      if (xmlGetLastError())
        xmlResetLastError();

      Xop = NULL;
      } // endif Xop

    if (NlXop) {
      xmlXPathFreeObject(NlXop);

      if (xmlGetLastError())
        xmlResetLastError();

      NlXop = NULL;
      } // endif NlXop

    if (Ctxp) {
      xmlXPathFreeContext(Ctxp);

      if (xmlGetLastError())
        xmlResetLastError();

      Ctxp = NULL;
      } // endif Ctxp

    } // endif xp

  CloseXML2File(g, xp, false);
	CloseZip();
  } // end of Close

/******************************************************************/
/* Evaluate the passed Xpath from the passed context node.        */
/******************************************************************/
xmlNodeSetPtr LIBXMLDOC::GetNodeList(PGLOBAL g, xmlNodePtr np, char *xp)
  {
  const xmlError *xerr;
  xmlNodeSetPtr nl;

  if (trace(1))
    htrc("GetNodeList: %-.256s np=%p\n", xp, np);

  if (!Ctxp) {
    // Init Xpath
    if (trace(1))
      htrc("Calling xmlPathInit\n");

    xmlInitParser();

    if (trace(1))
      htrc("Calling xmlXPathNewContext Docp=%p\n", Docp);

    // Create xpath evaluation context
    if (!(Ctxp = xmlXPathNewContext(Docp))) {
      strcpy(g->Message, MSG(XPATH_CNTX_ERR));

      if (trace(1))
        htrc("Context error: %-.256s\n", g->Message);

      return NULL;
      } // endif xpathCtx

    // Register namespaces from list (if any)
    for (PNS nsp = Namespaces; nsp; nsp = nsp->Next) {
      if (trace(1))
        htrc("Calling xmlXPathRegisterNs Prefix=%-.256s Uri=%-.512s\n", 
             nsp->Prefix, nsp->Uri);

      if (xmlXPathRegisterNs(Ctxp, BAD_CAST nsp->Prefix,
                                   BAD_CAST nsp->Uri)) {
        snprintf(g->Message, sizeof(g->Message), MSG(REGISTER_ERR), nsp->Prefix, nsp->Uri);

        if (trace(1))
          htrc("Ns error: %-.256s\n", g->Message);

        return NULL;
        } // endif Registering

      } // endfor nsp

    } // endif Ctxp

  if (Xop) {
    if (trace(1))
      htrc("Calling xmlXPathFreeNodeSetList Xop=%p NOFREE=%d\n", 
                                            Xop, Nofreelist);

    if (Nofreelist) {
      // Making Nlist that must not be freed yet
//    xmlXPathFreeNodeSetList(Xop);       // Caused memory leak
      assert(!NlXop);
      NlXop = Xop;                        // Freed on closing
      Nofreelist = false;
    } else
      xmlXPathFreeObject(Xop);            // Caused node not found

    if ((xerr = xmlGetLastError())) {
      strcpy(g->Message, xerr->message);
      xmlResetLastError();
      return NULL;
      } // endif xerr

    } // endif Xop

  // Set the context to the calling node
  Ctxp->node = np;

  if (trace(1))
    htrc("Calling xmlXPathEval %-.256s Ctxp=%p\n", xp, Ctxp);

  // Evaluate table xpath
  if (!(Xop = xmlXPathEval(BAD_CAST xp, Ctxp))) {
    snprintf(g->Message, sizeof(g->Message), MSG(XPATH_EVAL_ERR), xp);

    if (trace(1))
      htrc("Path error: %-.256s\n", g->Message);

    return NULL;
  } else
    nl = Xop->nodesetval;

  if (trace(1))
    htrc("GetNodeList nl=%p n=%p\n", nl, (nl) ? nl->nodeNr : 0);

  return nl;
  } // end of GetNodeList

#if 0                                // Not used anymore
/******************************************************************/
/*  CheckDocument: check if the document is ok to dump.           */
/*  Currently this does the dumping of the document.              */
/******************************************************************/
bool LIBXMLDOC::CheckDocument(FILE *of, xmlNodePtr np)
  {
  int  n;
  bool b;

  if (!np)
    return true;

  if (np->type == XML_ELEMENT_NODE) {
    n = fprintf(of, "<%s", np->name);
    b = CheckDocument(of, (xmlNodePtr)np->properties);

    if (np->children)
      n = fprintf(of, ">");
    else
      n = fprintf(of, "/>");

  } else if (np->type == XML_ATTRIBUTE_NODE)
    n = fprintf(of, " %s=\"", np->name);
  else if (np->type == XML_TEXT_NODE)
    n = fprintf(of, "%s", Encode(NULL, (char*)np->content));
  else if (np->type == XML_COMMENT_NODE)
    n = fprintf(of, "%s", Encode(NULL, (char*)np->content));

  b = CheckDocument(of, np->children);

  if (np->type == XML_ATTRIBUTE_NODE)
    n = fprintf(of, "\"");
  else if (!b && np->type == XML_ELEMENT_NODE)
    n = fprintf(of, "</%s>", np->name);

  b = CheckDocument(of, np->next);
  return false;
  } // end of CheckDocument

/******************************************************************/
/*  Convert node or attribute content to latin characters.        */
/******************************************************************/
int LIBXMLDOC::Decode(xmlChar *cnt, char *buf, int n)
  {
  const char *txt = (const char *)cnt;
  uint   dummy_errors;
  uint32 len= copy_and_convert(buf, n, &my_charset_utf8mb3_general_ci, txt,
                               strlen(txt), &my_charset_utf8mb3_general_ci,
                               &dummy_errors);
  buf[len]= '\0';
  return 0;
  } // end of Decode

/******************************************************************/
/*  Convert node or attribute content to latin characters.        */
/******************************************************************/
xmlChar *LIBXMLDOC::Encode(PGLOBAL g, char *txt)
  {
  const CHARSET_INFO *ics= &my_charset_utf8mb3_general_ci;
  const CHARSET_INFO *ocs= &my_charset_utf8mb3_general_ci;
  size_t      i = strlen(txt);
  size_t      o = i * ocs->mbmaxlen / ics->mbmaxlen + 1;
  char        *buf;
  if (g) {
    buf = (char*)PlugSubAlloc(g, NULL, o);
  } else {
    o = 1024;
    buf = Buf;
  } // endif g
  uint dummy_errors;
  uint32 len= copy_and_convert(buf, o, ocs,
                               txt, i, ics,
                               &dummy_errors);
  buf[len]= '\0';
  return BAD_CAST buf;
  } // end of Encode
#endif // 0

/* ---------------------- class XML2NODE ------------------------ */

/******************************************************************/
/*  XML2NODE constructor.                                         */
/******************************************************************/
XML2NODE::XML2NODE(PXDOC dp, xmlNodePtr np) : XMLNODE(dp)
  {
  Docp = ((PXDOC2)dp)->Docp;
  Content = NULL;
  Nodep = np;
  } // end of XML2NODE constructor

int XML2NODE::GetType(void)
  {
  if (trace(1))
    htrc("GetType type=%d\n", Nodep->type);

  return Nodep->type;
  }  // end of GetType

/******************************************************************/
/*  Return the node class of next sibling of the node.            */
/******************************************************************/
PXNODE XML2NODE::GetNext(PGLOBAL g)
  {
  if (trace(1))
    htrc("GetNext\n");

  if (!Nodep->next)
    Next = NULL;
  else // if (!Next)
    Next = new(g) XML2NODE(Doc, Nodep->next);

  return Next;
  } // end of GetNext

/******************************************************************/
/*  Return the node class of first children of the node.          */
/******************************************************************/
PXNODE XML2NODE::GetChild(PGLOBAL g)
  {
  if (trace(1))
    htrc("GetChild\n");

  if (!Nodep->children)
    Children = NULL;
  else // if (!Children)
    Children = new(g) XML2NODE(Doc, Nodep->children);

  return Children;
  } // end of GetChild

/******************************************************************/
/*  Return the content of a node and subnodes.                    */
/******************************************************************/
RCODE XML2NODE::GetContent(PGLOBAL g, char *buf, int len)
  {
  RCODE rc = RC_OK;

  if (trace(1))
    htrc("GetContent\n");

  if (Content)
    xmlFree(Content);

  if ((Content = xmlNodeGetContent(Nodep))) {
    char *p1 = (char*)Content, *p2 = buf;
    bool  b = false;

    // Copy content eliminating extra characters
    for (; *p1; p1++)
      if ((p2 - buf) < len) {
        if (strchr(" \t\r\n", *p1)) {
          if (b) {
            // This to have one blank between sub-nodes
            *p2++ = ' ';
            b = false;
            }  // endif b
    
        } else {
          *p2++ = *p1;
          b = true;
        } // endif p1

      } else {
        snprintf(g->Message, sizeof(g->Message), "Truncated %-.256s content", Nodep->name);
        rc = RC_INFO;
      } // endif len

    *p2 = 0;

    if (trace(1))
      htrc("GetText buf='%-.256s' len=%d\n", buf, len);

    xmlFree(Content);
    Content = NULL;
  } else
    *buf = '\0';

  if (trace(1))
    htrc("GetContent: %-.256s\n", buf);

  return rc;
  } // end of GetContent

/******************************************************************/
/*  Set the content of a node.                                    */
/******************************************************************/
bool XML2NODE::SetContent(PGLOBAL g, char *txtp, int len)
  {
  if (trace(1))
    htrc("SetContent: %-.256s\n", txtp);

  xmlChar *buf = xmlEncodeEntitiesReentrant(Docp, BAD_CAST txtp);

  if (trace(1))
    htrc("SetContent: %-.256s -> %-.256s\n", txtp, buf);

  xmlNodeSetContent(Nodep, buf);
  xmlFree(buf);
  return false;
  } // end of SetContent

/******************************************************************/
/*  Return a clone of this node.                                  */
/******************************************************************/
PXNODE XML2NODE::Clone(PGLOBAL g, PXNODE np)
  {
  if (trace(1))
    htrc("Clone: np=%p\n", np);

  if (np) {
    ((PNODE2)np)->Nodep = Nodep;
    return np;
  } else
    return new(g) XML2NODE(Doc, Nodep);

  } // end of Clone

/******************************************************************/
/*  Return the list of all or matching children that are elements.*/
/******************************************************************/
PXLIST XML2NODE::GetChildElements(PGLOBAL g, char *xp, PXLIST lp)
  {
  if (trace(1))
    htrc("GetChildElements: %-.256s\n", xp);

  return SelectNodes(g, (xp) ? xp : (char*)"*", lp);
  } // end of GetChildElements

/******************************************************************/
/*  Return the list of nodes verifying the passed Xpath.          */
/******************************************************************/
PXLIST XML2NODE::SelectNodes(PGLOBAL g, char *xp, PXLIST lp)
  {
  if (trace(1))
    htrc("SelectNodes: %-.256s\n", xp);

  xmlNodeSetPtr nl = ((PXDOC2)Doc)->GetNodeList(g, Nodep, xp);

  if (lp) {
    ((PLIST2)lp)->Listp = nl;
    return lp;
  } else
    return new(g) XML2NODELIST(Doc, nl);

  } // end of SelectNodes

/******************************************************************/
/*  Return the first node verifying the passed Xapth.             */
/******************************************************************/
PXNODE XML2NODE::SelectSingleNode(PGLOBAL g, char *xp, PXNODE np)
  {
  if (trace(1))
    htrc("SelectSingleNode: %-.256s\n", xp);

  xmlNodeSetPtr nl = ((PXDOC2)Doc)->GetNodeList(g, Nodep, xp);

  if (nl && nl->nodeNr) {
    if (np) {
      ((PNODE2)np)->Nodep = nl->nodeTab[0];
      return np;
    } else
      return new(g) XML2NODE(Doc, nl->nodeTab[0]);

  } else
    return NULL;

  } // end of SelectSingleNode

/******************************************************************/
/*  Return the node attribute with the specified name.            */
/******************************************************************/
PXATTR XML2NODE::GetAttribute(PGLOBAL g, char *name, PXATTR ap)
  {
  xmlAttrPtr atp;

  if (trace(1))
    htrc("GetAttribute: %-.256s\n", SVP(name));

  if (name)
    atp = xmlHasProp(Nodep, BAD_CAST name);
  else
    atp = Nodep->properties;


  if (atp) {
    if (ap) {
      ((PATTR2)ap)->Atrp = atp;
      ((PATTR2)ap)->Parent = Nodep;
      return ap;
    } else
      return new(g) XML2ATTR(Doc, atp, Nodep);

  } else
    return NULL;

  } // end of GetAttribute

/******************************************************************/
/*  Add a new child node to this node and return it.              */
/******************************************************************/
PXNODE XML2NODE::AddChildNode(PGLOBAL g, PCSZ name, PXNODE np)
  {
  char *p, *pn, *pf = NULL, *nmp = PlugDup(g, name);

  if (trace(1))
    htrc("AddChildNode: %-.256s\n", name);

  // Is a prefix specified
  if ((pn = strchr(nmp, ':'))) {
    pf = nmp;
    *pn++ = '\0';                    // Separate name from prefix
  } else
    pn = nmp;

  // If name has the format m[n] only m is taken as node name
  if ((p = strchr(pn, '[')))
    p = BufAlloc(g, pn, int(p - pn));
  else
    p = pn;

  xmlNodePtr nop = xmlNewChild(Nodep, NULL, BAD_CAST p, NULL);

  if (!nop)
    return NULL;

  if (pf) {
    // Prefixed name, is it the default NS prefix?
    if (Doc->DefNs && !strcmp(pf, Doc->DefNs))
      pf = NULL;                        // Default namespace

    xmlNsPtr nsp = xmlSearchNs(Docp, nop, BAD_CAST pf);

    if (!nsp)
      nsp = xmlNewNs(nop, NULL, BAD_CAST pf);

    // Set node namespace
    nop->ns = nsp;
    *(--p) = ':';                       // Restore Xname
  } else if (Doc->DefNs && xmlSearchNs(Docp, nop, NULL))
    // Not in default namespace
    nop->ns = xmlNewNs(nop, BAD_CAST "", NULL);

  if (np)
    ((PNODE2)np)->Nodep = nop;
  else
    np = new(g) XML2NODE(Doc, nop);

  return NewChild(np);
  } // end of AddChildNode

/******************************************************************/
/*  Add a new property to this node and return it.                */
/******************************************************************/
PXATTR XML2NODE::AddProperty(PGLOBAL g, char *name, PXATTR ap)
  {
  if (trace(1))
    htrc("AddProperty: %-.256s\n", name);

  xmlAttrPtr atp = xmlNewProp(Nodep, BAD_CAST name, NULL);

  if (atp) {
    if (ap) {
      ((PATTR2)ap)->Atrp = atp;
      ((PATTR2)ap)->Parent = Nodep;
      return ap;
    } else
      return new(g) XML2ATTR(Doc, atp, Nodep);

  } else
    return NULL;

  } // end of AddProperty

/******************************************************************/
/*  Add a new text node to this node.                             */
/******************************************************************/
void XML2NODE::AddText(PGLOBAL g, PCSZ txtp)
  {
  if (trace(1))
    htrc("AddText: %-.256s\n", txtp);

  // This is to avoid a blank line when inserting a new line
  xmlNodePtr np = xmlGetLastChild(Nodep);

  if (np && np->type == XML_TEXT_NODE) {
    xmlUnlinkNode(np);
    xmlFreeNode(np);
    } // endif type

  // Add the new text
  xmlAddChild(Nodep, xmlNewText(BAD_CAST txtp));
  } // end of AddText

/******************************************************************/
/*  Remove a child node from this node.                           */
/******************************************************************/
void XML2NODE::DeleteChild(PGLOBAL g, PXNODE dnp)
  {
  const xmlError *xerr;

  if (trace(1))
    htrc("DeleteChild: node=%p\n", dnp);

  xmlNodePtr np = ((PNODE2)dnp)->Nodep;
  xmlNodePtr text = np->next;

  // This is specific to row nodes
  if (text && text->type == XML_TEXT_NODE) {
    xmlUnlinkNode(text);

    if ((xerr = xmlGetLastError()))
      goto err;

    xmlFreeNode(text);

    if ((xerr = xmlGetLastError()))
      goto err;

    } // endif type

  xmlUnlinkNode(np);

  if ((xerr = xmlGetLastError()))
    goto err;

  xmlFreeNode(np);

  if ((xerr = xmlGetLastError()))
    goto err;

  Delete(dnp);

  if ((xerr = xmlGetLastError()))
    goto err;

  return;

err:
  if (trace(1))
    htrc("DeleteChild: errmsg=%-.256s\n", xerr->message);

  xmlResetLastError();
  } // end of DeleteChild

/* -------------------- class XML2NODELIST ---------------------- */

/******************************************************************/
/*  XML2NODELIST constructor.                                     */
/******************************************************************/
XML2NODELIST::XML2NODELIST(PXDOC dp, xmlNodeSetPtr lp)
            : XMLNODELIST(dp)
  {
  Listp = lp;
  } // end of XML2NODELIST constructor

/******************************************************************/
/*  Return the length of the list.                                */
/******************************************************************/
int XML2NODELIST::GetLength(void)
  {
  return (Listp) ? Listp->nodeNr : 0;
  } // end of GetLength

/******************************************************************/
/*  Return the nth element of the list.                           */
/******************************************************************/
PXNODE XML2NODELIST::GetItem(PGLOBAL g, int n, PXNODE np)
  {
  if (trace(1))
    htrc("GetItem: %d\n", n);

  if (!Listp || Listp->nodeNr <= n)
    return NULL;

  if (np) {
    ((PNODE2)np)->Nodep = Listp->nodeTab[n];
    return np;
  } else
    return new(g) XML2NODE(Doc, Listp->nodeTab[n]);

  } // end of GetItem

/******************************************************************/
/*  Reset the pointer on the deleted item.                        */
/******************************************************************/
bool XML2NODELIST::DropItem(PGLOBAL g, int n)
  {
  if (trace(1))
    htrc("DropItem: n=%d\n", n);

  // We should do something here
  if (!Listp || Listp->nodeNr <= n)
    return true;

  Listp->nodeTab[n] = NULL;   // This was causing Valgrind warning
  return false;
  } // end of DropItem

/* ---------------------- class XML2ATTR ------------------------ */

/******************************************************************/
/*  XML2ATTR constructor.                                         */
/******************************************************************/
XML2ATTR::XML2ATTR(PXDOC dp, xmlAttrPtr ap, xmlNodePtr np)
        : XMLATTRIBUTE(dp)
  {
  Atrp = ap;
  Parent = np;
  } // end of XML2ATTR constructor

/******************************************************************/
/*  Return the next sibling of the attribute.                     */
/******************************************************************/
PXATTR XML2ATTR::GetNext(PGLOBAL g)
  {
  if (trace(1))
    htrc("Attr GetNext\n");

  if (!Atrp->next)
    return NULL;
  else
    return new(g) XML2ATTR(Doc, Atrp->next, Atrp->parent);

  } // end of GetNext

/******************************************************************/
/*  Return the text of an attribute.                              */
/******************************************************************/
RCODE XML2ATTR::GetText(PGLOBAL g, char *buf, int len)
  {
  RCODE    rc = RC_OK;
  xmlChar *txt;

  if (trace(1))
    htrc("GetText\n");

  if ((txt = xmlGetProp(Atrp->parent, Atrp->name))) {
    // Copy the text to the buffer
    if (strlen((char*)txt) >= (unsigned)len) {
      memcpy(buf, txt, len - 1);
      buf[len - 1] = 0;
      snprintf(g->Message, sizeof(g->Message), "Truncated %-.256s content", Atrp->name);
      rc = RC_INFO;
    } else
      strcpy(buf, (const char*)txt);

    xmlFree(txt);
  } else
    *buf = '\0';

  if (trace(1))
    htrc("GetText: %-.256s\n", buf);

  return rc;
  } // end of GetText

/******************************************************************/
/*  Set the content of an attribute.                              */
/******************************************************************/
bool XML2ATTR::SetText(PGLOBAL g, char *txtp, int len)
  {
  if (trace(1))
    htrc("SetText: %-.256s %d\n", txtp, len);

  xmlSetProp(Parent, Atrp->name, BAD_CAST txtp);
  return false;
  } // end of SetText
