/* $Id: xml.c,v 1.10 2005/07/11 14:50:41 agraef Exp $ */

/* This file is part of the Q programming system.

   The Q programming system is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   The Q programming system is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */

#if defined (HAVE_CONFIG_H)
#  include "config.h"
#endif

/* system headers */

#include <stdio.h>
#include <ctype.h>
#include <math.h>

/* check for standard C headers */
#if STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
#endif

#ifdef HAVE_MALLOC_H
#include <stdlib.h>
#endif

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif

#ifdef DMALLOC
#include <dmalloc.h>
#endif

#include <libq.h>
#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxslt/xslt.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>

MODULE(xml)

/* initialization *****************************************************************/

/* cache symbol names */

static int sym_element, sym_element_text, sym_text, sym_cdata, sym_comment,
  sym_attr, sym_entity_ref, sym_pi,
  
  sym_doctype,
  
  sym_undefined_element, sym_empty_element, sym_any_element, sym_std_element,
  sym_mixed_element,
  
  sym_pcdata, sym_mksequence, sym_mkunion, sym_opt, sym_mult, sym_plus,
  
  sym_cdata_attr, sym_id_attr, sym_idref_attr, sym_idrefs_attr,
  sym_entity_attr, sym_entities_attr, sym_nmtoken_attr,
  sym_nmtokens_attr, sym_enum_attr, sym_notation_attr,
  
  sym_default, sym_required, sym_implied, sym_fixed,
  
  sym_int_entity, sym_int_param_entity, sym_ext_entity, sym_ext_param_entity;

INIT(xml)
{
  sym_element = sym(element);
  sym_element_text = sym(element_text);
  sym_text = sym(text);
  sym_cdata = sym(cdata);
  sym_comment = sym(comment);
  sym_attr = sym(attr);
  sym_entity_ref = sym(entity_ref);
  sym_pi = sym(pi);
  
  sym_doctype = sym(doctype);
  
  sym_undefined_element = sym(undefined_element);
  sym_empty_element = sym(empty_element);
  sym_any_element = sym(any_element);
  sym_std_element = sym(std_element);
  sym_mixed_element = sym(mixed_element);
  
  sym_pcdata = sym(pcdata);
  sym_mksequence = sym(mksequence);
  sym_mkunion = sym(mkunion);
  sym_opt = sym(opt);
  sym_mult = sym(mult);
  sym_plus = sym(plus);
  
  sym_cdata_attr = sym(cdata_attr);
  sym_id_attr = sym(id_attr);
  sym_idref_attr = sym(idref_attr);
  sym_idrefs_attr = sym(idrefs_attr);
  sym_entity_attr = sym(entity_attr);
  sym_entities_attr = sym(entities_attr);
  sym_nmtoken_attr = sym(nmtoken_attr);
  sym_nmtokens_attr = sym(nmtokens_attr);
  sym_enum_attr = sym(enum_attr);
  sym_notation_attr = sym(notation_attr);
  
  sym_default = sym(default);
  sym_required = sym(required);
  sym_implied = sym(implied);
  sym_fixed = sym(fixed);
  
  sym_int_entity = sym(int_entity);
  sym_int_param_entity = sym(int_param_entity);
  sym_ext_entity = sym(ext_entity);
  sym_ext_param_entity = sym(ext_param_entity);
}

/* static buffer for parsing and constructing qualified names */

static int bufsz = -1;
static char *buf = NULL;

static char *mkbuf(int size)
{
  if (!buf) {
    buf = malloc(size+1);
    if (buf)
      bufsz = size+1;
    else
      return NULL;
  } else if (size+1 > bufsz) {
    char *buf1 = realloc(buf, size+1);
    if (buf1) {
      buf = buf1;
      bufsz = size+1;
    } else
      return NULL;
  }
  return buf;
}

FINI(xml)
{
  if (buf) {
    free(buf);
    buf = NULL;
    bufsz = -1;
  }
}

/* helper functions ***************************************************************/

/* parser flags */

#define XML_DTDLOAD	0x01
#define XML_DTDVALID	0x02
#define XML_PEDANTIC	0x04
#define XML_SUBENT	0x08
#define XML_NOBLANKS	0x10

#ifdef _WIN32
/* correct garbled declarations in libxml/globals.h */
__declspec(dllimport) int xmlLoadExtDtdDefaultValue;
__declspec(dllimport) int xmlDoValidityCheckingDefaultValue;
__declspec(dllimport) int xmlPedanticParserDefaultValue;
__declspec(dllimport) int xmlSubstituteEntitiesDefaultValue;
__declspec(dllimport) int xmlKeepBlanksDefaultValue;
__declspec(dllimport) int xmlIndentTreeOutput;
#endif

static unsigned set_flags(unsigned flags)
{
  unsigned oldflags = (!!xmlLoadExtDtdDefaultValue)*XML_DTDLOAD |
    (!!xmlDoValidityCheckingDefaultValue)*XML_DTDVALID |
    (!!xmlPedanticParserDefaultValue)*XML_PEDANTIC |
    (!!xmlSubstituteEntitiesDefaultValue)*XML_SUBENT |
    (!xmlKeepBlanksDefaultValue)*XML_NOBLANKS;
  xmlLoadExtDtdDefaultValue = !!(flags&XML_DTDLOAD);
  xmlDoValidityCheckingDefaultValue = !!(flags&XML_DTDVALID);
  xmlPedanticParserDefaultValue = !!(flags&XML_PEDANTIC);
  xmlSubstituteEntitiesDefaultValue = !!(flags&XML_SUBENT);
  xmlKeepBlanksDefaultValue = !(flags&XML_NOBLANKS);
  return oldflags;
}

/* supported node types */

static supported(xmlNodePtr node)
{
  switch (node->type) {
  case XML_ELEMENT_NODE:
  case XML_ATTRIBUTE_NODE:
  case XML_ENTITY_REF_NODE:
  case XML_TEXT_NODE:
  case XML_CDATA_SECTION_NODE:
  case XML_COMMENT_NODE:
  case XML_PI_NODE:
  case XML_DTD_NODE:
  case XML_ELEMENT_DECL:
  case XML_ATTRIBUTE_DECL:
  case XML_ENTITY_DECL:
    return 1;
  default:
    return 0;
  }
}

/* maintain node and doc references */

static void chknode(xmlNodePtr node)
{
  if (node->_private)
    xmlUnlinkNode(node);
  else {
    xmlNodePtr next;
    for (node = node->children; node; node = next) {
      next = node->next;
      chknode(node);
    }
  }
}

static void newdocref(xmlNodePtr node)
{
  if (node != (xmlNodePtr)node->doc && node->doc && node->doc->_private)
    newref(node->doc->_private);
}

static void freedocref(xmlNodePtr node)
{
  if (node != (xmlNodePtr)node->doc && node->doc && node->doc->_private)
    freeref(node->doc->_private);
}

static void freenode(xmlNodePtr node)
{
  if (!supported(node))
    /* Better don't touch this one ... There's a potential mem leak here, but
       we shouldn't get an unsupported node type in the first place. */
    ;
  else if (node->_private)
    /* Dangling reference -- will be collected later. */
    ;
  else if (node->type == XML_ATTRIBUTE_NODE)
    xmlFreeProp((xmlAttrPtr)node);
  else
    xmlFreeNode(node);
}

/* qualified element and attribute names */

static const char *splitname(const char *name, const char **prefix)
{
  char *pos = strchr(name, ':');
  if (pos) {
    int p = pos-name;
    char *buf = mkbuf(strlen(name));
    if (!buf) {
      *prefix = NULL;
      return NULL;
    }
    strcpy(buf, name);
    buf[p] = 0;
    *prefix = buf;
    return buf+p+1;
  } else {
    *prefix = NULL;
    return name;
  }
}

static const char *mkname(const char *name, const char *prefix)
{
  char *buf;
  if (!prefix || !*prefix)
    return name;
  buf = mkbuf(strlen(name)+strlen(prefix));
  if (buf) sprintf(buf, "%s:%s", prefix, name);
  return buf;
}

static const char *mkname2(const char *name, xmlNsPtr ns)
{
  if (ns)
    return mkname(name, ns->prefix);
  else
    return name;
}

static const char *nodename(xmlNodePtr node)
{
  return mkname2(node->name, node->ns);
}

static const char *attrname(xmlAttrPtr attr)
{
  return mkname2(attr->name, attr->ns);
}

/* namespaces */

static xmlNsPtr mkns(xmlDocPtr doc, xmlNodePtr parent, xmlNodePtr node,
		     const char *prefix)
{
  xmlNsPtr ns = xmlSearchNs(doc, node, prefix);
  if (!ns && parent && node->parent != parent)
    ns = xmlSearchNs(doc, parent, prefix);
  return ns;
}

/* parse namespaces, attributes and node infos */

static int parse_namespace(expr namespace, char **prefix, char **href)
{
  expr *xs;
  int n;
  *prefix = *href = NULL;
  return istuple(namespace, &n, &xs) && n == 2 &&
    (isvoid(xs[0]) || isstr(xs[0], prefix)) && isstr(xs[1], href);
}

static int parse_namespaces(xmlNodePtr node, expr namespaces)
{
  expr hd, tl;
  char *prefix, *href;
  while (iscons(namespaces, &hd, &tl) && parse_namespace(hd, &prefix, &href) &&
	 (prefix || href)) {
    xmlNsPtr ns = xmlNewNs(node, href, prefix);
    if (!ns)
      return 0;
    namespaces = tl;
  }
  return isnil(namespaces);
}

static int parse_attr(expr attr, char **key, char **val)
{
  expr *xs;
  int n;
  return istuple(attr, &n, &xs) && n == 2 &&
    isstr(xs[0], key) && isstr(xs[1], val);
}

static int parse_attrs(xmlDocPtr doc, xmlNodePtr parent, xmlNodePtr node, expr attrs)
{
  expr hd, tl;
  char *key, *val;
  while (iscons(attrs, &hd, &tl) && parse_attr(hd, &key, &val)) {
    const char *prefix, *name = splitname(key, &prefix);
    xmlNsPtr ns = mkns(doc, parent, node, prefix);
    if (prefix && !ns) return 0;
    if (!xmlNewNsProp(node, ns, name, val)) return 0;
    attrs = tl;
  }
  return isnil(attrs);
}

static xmlNodePtr parse_info(xmlDocPtr doc, xmlNodePtr parent, expr info)
{
  xmlNodePtr node = NULL;
  char *s, *t;
  expr f, g, h, k, x, y, z, u;
  if (!isapp(info, &f, &x))
    return NULL;
  if (issym(f, sym_text) && isstr(x, &s))
    node = xmlNewText(s);
  else if (issym(f, sym_cdata) && isstr(x, &s))
    node = xmlNewCDataBlock(doc, s, strlen(s));
  else if (issym(f, sym_comment) && isstr(x, &s))
    node = xmlNewComment(s);
  else if (issym(f, sym_entity_ref) && isstr(x, &s))
    node = xmlNewReference(doc, s);
  else if (!isapp(f, &g, &y))
    return NULL;
  else if (issym(g, sym_pi) && isstr(y, &s) && isstr(x, &t))
    node = xmlNewPI(s, t);
  else if (!isapp(g, &h, &z))
    return NULL;
  else if (issym(h, sym_element) && isstr(z, &s)) {
    const char *prefix, *name = splitname(s, &prefix);
    node = xmlNewNode(NULL, name);
    if (!node) return NULL;
    if (!parse_namespaces(node, y)) {
      xmlFreeNode(node);
      return NULL;
    }
    if (!parse_attrs(doc, parent, node, x)) {
      xmlFreeNode(node);
      return NULL;
    }
    node->ns = mkns(doc, parent, node, prefix);
    if (prefix && !node->ns) {
      xmlFreeNode(node);
      return NULL;
    }
  } else if (!isapp(h, &k, &u))
    return NULL;
  else if (issym(k, sym_element_text) && isstr(u, &s) && isstr(x, &t)) {
    const char *prefix, *name = splitname(s, &prefix);
    node = xmlNewNode(NULL, name);
    if (!node) return NULL;
    if (!parse_namespaces(node, z)) {
      xmlFreeNode(node);
      return NULL;
    }
    if (!parse_attrs(doc, parent, node, y)) {
      xmlFreeNode(node);
      return NULL;
    }
    node->ns = mkns(doc, parent, node, prefix);
    if (prefix && !node->ns) {
      xmlFreeNode(node);
      return NULL;
    }
    xmlNodeAddContent(node, t);
  }
  return node;
}

/* construct different kinds of return values */

static expr mkstr2(const char *s)
{
  if (s)
    return mkstr(strdup(s));
  else
    return mkvoid;
}

static expr mknode(xmlNodePtr node)
{
  if (!node)
    return mkvoid;
  else if (node->_private)
    return node->_private;
  else {
    node->_private = mkobj(type(XMLNode), node);
    newdocref(node);
    return node->_private;
  }
}

static expr mkextid(const char *extid, const char *sysid)
{
  if (extid && sysid)
    return mktuplel(2, mkstr2(extid), mkstr2(sysid));
  else if (extid)
    return mkstr2(extid);
  else if (sysid)
    return mkstr2(sysid);
  else
    return mkvoid;
}

static expr mkcontent(xmlElementContentPtr content)
{
  expr c;
  switch (content->type) {
  case XML_ELEMENT_CONTENT_PCDATA:
    c = mksym(sym_pcdata);
    break;
  case XML_ELEMENT_CONTENT_ELEMENT:
    c = mkstr2(mkname(content->name, content->prefix));
    break;
  case XML_ELEMENT_CONTENT_SEQ:
    c = mkapp(mkapp(mksym(sym_mksequence), mkcontent(content->c1)),
	      mkcontent(content->c2));
    break;
  case XML_ELEMENT_CONTENT_OR:
    c = mkapp(mkapp(mksym(sym_mkunion), mkcontent(content->c1)),
	      mkcontent(content->c2));
    break;
  default:
    return __FAIL;
  }
  switch (content->ocur) {
  case XML_ELEMENT_CONTENT_ONCE:
    return c;
  case XML_ELEMENT_CONTENT_OPT:
    return mkapp(mksym(sym_opt), c);
  case XML_ELEMENT_CONTENT_MULT:
    return mkapp(mksym(sym_mult), c);
  case XML_ELEMENT_CONTENT_PLUS:
    return mkapp(mksym(sym_plus), c);
  default:
    return __FAIL;
  }
}

static expr mkenum(xmlEnumerationPtr ptr)
{
  int n = 0;
  xmlEnumerationPtr p;
  for (p = ptr; p; p = p->next) n++;
  if (n >= 0) {
    expr *xs;
    xs = malloc(n*sizeof(expr));
    if (!xs) return NULL;
    for (n = 0, p = ptr; p; p = p->next)
      xs[n++] = mkstr2(p->name);
    return mklistv(n, xs);
  } else
    return mknil;
}

/* XML interface ******************************************************************/

DESTRUCTOR(xml,XMLDoc,ptr)
{
  xmlDocPtr doc = (xmlDocPtr)ptr;
  xmlFreeDoc(doc);
}

DESTRUCTOR(xml,XMLNode,ptr)
{
  xmlNodePtr node = (xmlNodePtr)ptr;
  node->_private = NULL;
  if (!node->parent &&
      (!node->doc ||
       (xmlNodePtr)node->doc->extSubset != node &&
       (xmlNodePtr)node->doc->intSubset != node)) {
    xmlNodePtr n, next;
    for (n = node->children; n; n = next) {
      next = n->next;
      chknode(n);
    }
    freedocref(node);
    freenode(node);
  } else 
    freedocref(node);
}

FUNCTION(xml,xml_new_doc,argc,argv)
{
  char *s = NULL;
  char *extid = NULL, *sysid = NULL;
  expr *xs;
  int n;
  if (argc == 3 && (isvoid(argv[0]) || isstr(argv[0], &s)) &&
      (isvoid(argv[1]) || isstr(argv[1], &sysid) ||
       istuple(argv[1], &n, &xs) && n==2 && isstr(xs[0], &extid) &&
       isstr(xs[1], &sysid))) {
    xmlDocPtr doc = xmlNewDoc(s);
    xmlNodePtr root;
    if (!doc) return __FAIL;
    root = parse_info(doc, (xmlNodePtr)doc, argv[2]);
    if (!root) {
      xmlFreeDoc(doc);
      return __FAIL;
    }
    xmlDocSetRootElement(doc, root);
    if (!root->name) {
      xmlFreeDoc(doc);
      return __FAIL;
    }
    root->parent = (xmlNodePtr)doc;
    root->doc = doc;
    if ((extid || sysid)) {
      xmlDtdPtr dtd = xmlParseDTD(extid, sysid);
      if (!dtd) {
	xmlFreeDoc(doc);
	return __FAIL;
      }
      dtd->name = xmlStrdup(root->name); /* FIXME: do we need a qualid here? */
      doc->intSubset = dtd;
      if (doc->children == NULL)
	xmlAddChild((xmlNodePtr)doc, (xmlNodePtr)dtd);
      else
	xmlAddPrevSibling(doc->children, (xmlNodePtr)dtd);
    }
    doc->_private = mkobj(type(XMLDoc), doc);
    return doc->_private;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_load_file,argc,argv)
{
  char *s;
  unsigned long flags;
  if (argc == 2 && isstr(argv[0], &s) &&
      isuint(argv[1], &flags)) {
    unsigned oldflags = set_flags(flags);
    xmlDocPtr doc = xmlParseFile(s);
    set_flags(oldflags);
    if (doc) {
      doc->_private = mkobj(type(XMLDoc), doc);
      return doc->_private;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_load_string,argc,argv)
{
  char *s;
  unsigned long flags;
  if (argc == 2 && isstr(argv[0], &s) &&
      isuint(argv[1], &flags)) {
    unsigned oldflags = set_flags(flags);
    xmlDocPtr doc = xmlParseDoc(s);
    set_flags(oldflags);
    if (doc) {
      doc->_private = mkobj(type(XMLDoc), doc);
      return doc->_private;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_save_file,argc,argv)
{
  char *s, *enc = NULL;
  long compression = -1;
  xmlDocPtr doc;
  if (argc == 4 && isstr(argv[0], &s) &&
      isobj(argv[1], type(XMLDoc), (void**)&doc) &&
      xmlDocGetRootElement(doc) &&
      (isvoid(argv[2]) || isstr(argv[2], &enc)) &&
      (isvoid(argv[3]) || isint(argv[3], &compression))) {
    int save_compression = doc->compression, res,
      save_indent = xmlIndentTreeOutput;
    if (compression >= 0) doc->compression = compression;
    xmlIndentTreeOutput = 1;
    res = xmlSaveFormatFileEnc(s, doc, enc, 1);
    xmlIndentTreeOutput = save_indent;
    doc->compression = save_compression;
    if (res >= 0)
      return mkvoid;
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_save_string,argc,argv)
{
  xmlDocPtr doc;
  char *enc = NULL;
  if (argc == 2 && isobj(argv[0], type(XMLDoc), (void**)&doc) &&
      xmlDocGetRootElement(doc) &&
      (isvoid(argv[1]) || isstr(argv[1], &enc))) {
    xmlChar *s = NULL;
    int len, save_indent = xmlIndentTreeOutput;
    xmlIndentTreeOutput = 1;
    xmlDocDumpFormatMemoryEnc(doc, &s, &len, enc, 1);
    xmlIndentTreeOutput = save_indent;
    if (s)
      return mkstr(s);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_doc_info,argc,argv)
{
  xmlDocPtr doc;
  if (argc == 1 && isobj(argv[0], type(XMLDoc), (void**)&doc))
    return mktuplel(5, mkstr2(doc->version), mkstr2(doc->encoding),
		    mkstr2(doc->URL), mkint(doc->compression),
		    mkbool(doc->standalone));
  else
    return __FAIL;
}

FUNCTION(xml,xml_int_subset,argc,argv)
{
  xmlDocPtr doc;
  if (argc == 1 && isobj(argv[0], type(XMLDoc), (void**)&doc) &&
      doc->intSubset)
    return mknode((xmlNodePtr)doc->intSubset);
  else
    return __FAIL;
}

FUNCTION(xml,xml_ext_subset,argc,argv)
{
  xmlDocPtr doc;
  if (argc == 1 && isobj(argv[0], type(XMLDoc), (void**)&doc) &&
      doc->extSubset)
    return mknode((xmlNodePtr)doc->extSubset);
  else
    return __FAIL;
}

FUNCTION(xml,xml_root,argc,argv)
{
  xmlDocPtr doc;
  if (argc == 1 && isobj(argv[0], type(XMLDoc), (void**)&doc)) {
    xmlNodePtr root = xmlDocGetRootElement(doc);
    if (!root)
      return __FAIL;
    else
      return mknode(root);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_doc,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node)) {
    xmlDocPtr doc = node->doc;
    if (!doc || !doc->_private)
      return __FAIL;
    else
      return doc->_private;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_parent,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node)) {
    xmlNodePtr parent = node->parent;
    if (!parent)
      return __FAIL;
    else
      return mknode(parent);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_first,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ENTITY_REF_NODE &&
      node->type != XML_ATTRIBUTE_NODE) {
    xmlNodePtr first = node->children;
    if (!first)
      return __FAIL;
    else
      return mknode(first);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_last,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ENTITY_REF_NODE &&
      node->type != XML_ATTRIBUTE_NODE) {
    xmlNodePtr last = node->last;
    if (!last)
      return __FAIL;
    else
      return mknode(last);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_next,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node)) {
    xmlNodePtr next = node->next;
    if (!next)
      return __FAIL;
    else
      return mknode(next);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_prev,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node)) {
    xmlNodePtr prev = node->prev;
    if (!prev)
      return __FAIL;
    else
      return mknode(prev);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_first_attr,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node)) {
    xmlAttrPtr attr = node->properties;
    if (attr)
      return mknode((xmlNodePtr)attr);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_last_attr,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node)) {
    xmlAttrPtr attr = node->properties;
    if (attr) {
      while (attr->next)
	attr = attr->next;
      return mknode((xmlNodePtr)attr);
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_select,argc,argv)
{
  xmlDocPtr doc;
  char *s;
  if (argc == 2 && isobj(argv[0], type(XMLDoc), (void**)&doc) &&
      isstr(argv[1], &s)) {
    int i;
    expr nodes;
    xmlXPathContextPtr context;
    xmlXPathObjectPtr result;
    xmlNodeSetPtr nodeset;
    context = xmlXPathNewContext(doc);
    if (!context) return __FAIL;
    result = xmlXPathEvalExpression(s, context);
    xmlXPathFreeContext(context);
    if (!result || !(nodeset = result->nodesetval)) return __FAIL;
    nodes = mknil;
    for (i = nodeset->nodeNr; nodes && --i >= 0;) {
      xmlNodePtr nodeptr = nodeset->nodeTab[i];
      expr node;
      if (!nodeptr)
	node = NULL;
      else
	node = mknode(nodeptr);
      nodes = mkcons(node, nodes);
    }
    xmlXPathFreeObject(result);
    return nodes;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_node_info,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node)) {
    switch (node->type) {
    case XML_ELEMENT_NODE: {
      expr name = mkstr2(nodename(node));
      expr namespaces, attrs = mknil, val;
      xmlNsPtr nsdef = node->nsDef;
      xmlAttrPtr attr = node->properties;
      if (nsdef) {
	xmlNsPtr ns;
	expr *xs;
	int n = 0;
	for (ns = nsdef; ns; ns = ns->next) n++;
	xs = malloc(n*sizeof(expr));
	if (!xs) return __ERROR;
	for (n = 0, ns = nsdef; ns; ns = ns->next)
	  xs[n++] = mktuplel(2, mkstr2(ns->prefix), mkstr2(ns->href));
	namespaces = mklistv(n, xs);
      } else
	namespaces = mknil;
      if (attr) {
	while (attr->next)
	  attr = attr->next;
	while (attrs && attr) {
	  char *s = attr->children->content;
	  val = mkstr2(s);
	  attrs = mkcons(mktuplel(2, mkstr2(attrname(attr)), val), attrs);
	  attr = attr->prev;
	}
      }
      return mkapp(mkapp(mkapp(mksym(sym_element), name), namespaces), attrs);
    }
    case XML_ATTRIBUTE_NODE: {
      expr name = mkstr2(nodename(node));
      expr val = mkstr2(node->children->content);
      return mkapp(mkapp(mksym(sym_attr), name), val);
    }
    case XML_ENTITY_REF_NODE: {
      expr name = mkstr2(nodename(node));
      return mkapp(mksym(sym_entity_ref), name);
    }
    case XML_TEXT_NODE: {
      expr content = mkstr2(node->content);
      return mkapp(mksym(sym_text), content);
    }
    case XML_CDATA_SECTION_NODE: {
      expr content = mkstr2(node->content);
      return mkapp(mksym(sym_cdata), content);
    }
    case XML_COMMENT_NODE: {
      expr content = mkstr2(node->content);
      return mkapp(mksym(sym_comment), content);
    }
    case XML_PI_NODE: {
      expr name = mkstr2(nodename(node));
      expr content = mkstr2(node->content);
      return mkapp(mkapp(mksym(sym_pi), name), content);
    }
    case XML_DTD_NODE: {
      xmlDtdPtr dtd = (xmlDtdPtr)node;
      expr name = mkstr2(dtd->name);
      expr extid = mkextid(dtd->ExternalID, dtd->SystemID);
      return mkapp(mkapp(mksym(sym_doctype), name), extid);
    }
    case XML_ELEMENT_DECL: {
      xmlElementPtr element = (xmlElementPtr)node;
      expr name = mkstr2(mkname(element->name, element->prefix));
      switch (element->etype) {
      case XML_ELEMENT_TYPE_UNDEFINED:
	return mkapp(mksym(sym_undefined_element), name);
      case XML_ELEMENT_TYPE_EMPTY:
	return mkapp(mksym(sym_empty_element), name);
      case XML_ELEMENT_TYPE_ANY:
	return mkapp(mksym(sym_any_element), name);
      case XML_ELEMENT_TYPE_ELEMENT:
	return mkapp(mkapp(mksym(sym_std_element), name),
		     mkcontent(element->content));
      case XML_ELEMENT_TYPE_MIXED:
	return mkapp(mkapp(mksym(sym_mixed_element), name),
		     mkcontent(element->content));
      default:
	return __FAIL;
      }
    }
    case XML_ATTRIBUTE_DECL: {
      xmlAttributePtr attr = (xmlAttributePtr)node;
      expr name = mkstr2(mkname(attr->name, attr->prefix));
      expr elem_name = mkstr2(attr->elem);
      expr dflt;
      switch (attr->def) {
      case XML_ATTRIBUTE_NONE:
	dflt = mkapp(mksym(sym_default), mkstr2(attr->defaultValue));
	break;
      case XML_ATTRIBUTE_REQUIRED:
	dflt = mksym(sym_required);
	break;
      case XML_ATTRIBUTE_IMPLIED:
	dflt = mksym(sym_implied);
	break;
      case XML_ATTRIBUTE_FIXED:
	dflt = mkapp(mksym(sym_fixed), mkstr2(attr->defaultValue));
	break;
      default:
	return __FAIL;
      }
      switch (attr->atype) {
      case XML_ATTRIBUTE_CDATA:
	return mkapp(mkapp(mkapp(mksym(sym_cdata_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_ID:
	return mkapp(mkapp(mkapp(mksym(sym_id_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_IDREF:
	return mkapp(mkapp(mkapp(mksym(sym_idref_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_IDREFS:
	return mkapp(mkapp(mkapp(mksym(sym_idrefs_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_ENTITY:
	return mkapp(mkapp(mkapp(mksym(sym_entity_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_ENTITIES:
	return mkapp(mkapp(mkapp(mksym(sym_entities_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_NMTOKEN:
	return mkapp(mkapp(mkapp(mksym(sym_nmtoken_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_NMTOKENS:
	return mkapp(mkapp(mkapp(mksym(sym_nmtokens_attr), elem_name), name),
		     dflt);
      case XML_ATTRIBUTE_ENUMERATION:
	return mkapp(mkapp(mkapp(mkapp(mksym(sym_enum_attr), elem_name), name),
			   mkenum(attr->tree)), dflt);
      case XML_ATTRIBUTE_NOTATION:
	return mkapp(mkapp(mkapp(mkapp(mksym(sym_notation_attr), elem_name),
				 name),
			   mkenum(attr->tree)), dflt);
      default:
	return __FAIL;
      }
    }
    case XML_ENTITY_DECL: {
      xmlEntityPtr entity = (xmlEntityPtr)node;
      expr name = mkstr2(entity->name);
      expr content = mkstr2(entity->content);
      switch (entity->etype) {
      case XML_INTERNAL_GENERAL_ENTITY:
	return mkapp(mkapp(mksym(sym_int_entity), name), content);
      case XML_INTERNAL_PARAMETER_ENTITY:
	return mkapp(mkapp(mksym(sym_int_param_entity), name), content);
      case XML_EXTERNAL_GENERAL_PARSED_ENTITY: {
	expr extid = mkextid(entity->ExternalID, entity->SystemID);
	return mkapp(mkapp(mkapp(mksym(sym_ext_entity), name), extid), content);
      }
      case XML_EXTERNAL_GENERAL_UNPARSED_ENTITY: {
	expr extid = mkextid(entity->ExternalID, entity->SystemID);
	return mkapp(mkapp(mkapp(mksym(sym_ext_entity), name), extid), content);
      }
      case XML_EXTERNAL_PARAMETER_ENTITY: {
	expr extid = mkextid(entity->ExternalID, entity->SystemID);
	return mkapp(mkapp(mkapp(mksym(sym_ext_param_entity), name), extid),
		     content);
      }
      default:
	return __FAIL;
      }
    }
    default:
      return __FAIL;
    }
  } else {
#if 0
    printf("unrecognized node type %d\n", node->type);
#endif
    return __FAIL;
  }
}

FUNCTION(xml,xml_is_blank_node,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node))
    return mkbool(xmlIsBlankNode(node));
  else
    return __FAIL;
}

FUNCTION(xml,xml_node_base,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node) && node->doc) {
    char *s = xmlNodeGetBase(node->doc, node);
    if (s)
      return mkstr(s);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_node_path,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node)) {
    char *s = xmlGetNodePath(node);
    if (s)
      return mkstr(s);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_node_content,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node)) {
    char *s = xmlNodeGetContent(node);
    if (s)
      return mkstr(s);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_node_attr,argc,argv)
{
  char *s;
  xmlNodePtr node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      node->type == XML_ELEMENT_NODE &&
      isstr(argv[1], &s)) {
    const char *prefix, *name = splitname(s, &prefix);
    xmlNsPtr ns = mkns(node->doc, node->parent, node, prefix);
    char *t;
    if (prefix && !ns) return __FAIL;
    t = xmlGetNsProp(node, name, ns?ns->href:NULL);
    if (t)
      return mkstr(t);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_set_node_attr,argc,argv)
{
  char *s, *t;
  xmlNodePtr node;
  if (argc == 3 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      node->type == XML_ELEMENT_NODE &&
      isstr(argv[1], &s) && isstr(argv[2], &t)) {
    const char *prefix, *name = splitname(s, &prefix);
    xmlNsPtr ns = mkns(node->doc, node->parent, node, prefix);
    if (prefix && !ns) return __FAIL;
    if (xmlSetNsProp(node, ns, name, t))
      return mkvoid;
    else
      return __FAIL;
  } else
    return __FAIL;
}

/* These are reimplemented here, because the library versions are buggy
   (missing updates of prev pointers), and we also need to take care of
   dangling references in attribute node objects. */

static int
myxmlUnsetProp(xmlNodePtr node, const xmlChar *name)
{
  xmlAttrPtr prop = node->properties, prev = NULL;

  if ((node == NULL) || (name == NULL))
    return(-1);
  while (prop != NULL) {
    if ((xmlStrEqual(prop->name, name)) &&
	(prop->ns == NULL)) {
      if (prop->next) prop->next->prev = prev;
      if (prev == NULL)
	node->properties = prop->next;
      else
	prev->next = prop->next;
      prop->prev = prop->next = NULL;
      prop->parent = NULL;
      freenode((xmlNodePtr)prop);
      return(0);
    }
    prev = prop;
    prop = prop->next;
  }
  return(-1);
}

static int
myxmlUnsetNsProp(xmlNodePtr node, xmlNsPtr ns, const xmlChar *name)
{
  xmlAttrPtr prop = node->properties, prev = NULL;

  if ((node == NULL) || (name == NULL))
    return(-1);
  if (ns == NULL)
    return(myxmlUnsetProp(node, name));
  if (ns->href == NULL)
    return(-1);
  while (prop != NULL) {
    if ((xmlStrEqual(prop->name, name)) &&
	(((prop->ns == NULL) && (node->ns != NULL) &&
	  (xmlStrEqual(node->ns->href, ns->href))) ||
	 ((prop->ns != NULL) && (xmlStrEqual(prop->ns->href, ns->href))))) {
      if (prop->next) prop->next->prev = prev;
      if (prev == NULL)
	node->properties = prop->next;
      else
	prev->next = prop->next;
      prop->prev = prop->next = NULL;
      prop->parent = NULL;
      freenode((xmlNodePtr)prop);
      return(0);
    }
    prev = prop;
    prop = prop->next;
  }
  return(-1);
}

FUNCTION(xml,xml_unset_node_attr,argc,argv)
{
  char *s;
  xmlNodePtr node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      node->type == XML_ELEMENT_NODE &&
      isstr(argv[1], &s)) {
    const char *prefix, *name = splitname(s, &prefix);
    xmlNsPtr ns = mkns(node->doc, node->parent, node, prefix);
    if (prefix && !ns) return __FAIL;
    if (!myxmlUnsetNsProp(node, ns, name))
      return mkvoid;
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xml_replace,argc,argv)
{
  xmlNodePtr node, new_node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ATTRIBUTE_NODE &&
      (new_node = parse_info(node->doc, node->parent, argv[1]))) {
    xmlReplaceNode(node, new_node);
    return mknode(new_node);
  } else
    return __FAIL;
}

FUNCTION(xml,xml_add_first,argc,argv)
{
  xmlNodePtr node, new_node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ENTITY_REF_NODE &&
      node->type != XML_ATTRIBUTE_NODE &&
      (new_node = parse_info(node->doc, node, argv[1]))) {
    xmlNodePtr res, first = node->children;
    if (first)
      res = xmlAddPrevSibling(first, new_node);
    else
      res = xmlAddChild(node, new_node);
    if (res)
      return mknode(res);
    else {
      xmlFreeNode(new_node);
      return __FAIL;
    }
  } else
    return __FAIL;
}

FUNCTION(xml,xml_add_last,argc,argv)
{
  xmlNodePtr node, new_node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ENTITY_REF_NODE &&
      node->type != XML_ATTRIBUTE_NODE &&
      (new_node = parse_info(node->doc, node, argv[1]))) {
    xmlNodePtr res, last = node->last;
    if (last)
      res = xmlAddNextSibling(last, new_node);
    else
      res = xmlAddChild(node, new_node);
    if (res)
      return mknode(res);
    else {
      xmlFreeNode(new_node);
      return __FAIL;
    }
  } else
    return __FAIL;
}

FUNCTION(xml,xml_add_next,argc,argv)
{
  xmlNodePtr node, new_node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ATTRIBUTE_NODE &&
      (new_node = parse_info(node->doc, node->parent, argv[1]))) {
    xmlNodePtr res = xmlAddNextSibling(node, new_node);
    if (res)
      return mknode(res);
    else {
      xmlFreeNode(new_node);
      return __FAIL;
    }
  } else
    return __FAIL;
}

FUNCTION(xml,xml_add_prev,argc,argv)
{
  xmlNodePtr node, new_node;
  if (argc == 2 && isobj(argv[0], type(XMLNode), (void**)&node) &&
      supported(node) && node->type != XML_ATTRIBUTE_NODE &&
      (new_node = parse_info(node->doc, node->parent, argv[1]))) {
    xmlNodePtr res = xmlAddPrevSibling(node, new_node);
    if (res)
      return mknode(res);
    else {
      xmlFreeNode(new_node);
      return __FAIL;
    }
  } else
    return __FAIL;
}

FUNCTION(xml,xml_unlink,argc,argv)
{
  xmlNodePtr node;
  if (argc == 1 && isobj(argv[0], type(XMLNode), (void**)&node)) {
    xmlUnlinkNode(node);
    return mkvoid;
  } else
    return __FAIL;
}

/* XSLT interface *****************************************************************/

DESTRUCTOR(xml,XSLTStylesheet,ptr)
{
  xsltStylesheetPtr style = (xsltStylesheetPtr)ptr;
  xsltFreeStylesheet(style);
}

FUNCTION(xml,xslt_load_stylesheet,argc,argv)
{
  char *s = NULL;
  xmlDocPtr doc = NULL;
  if (argc == 1 &&
      (isstr(argv[0], &s) || isobj(argv[0], type(XMLDoc), (void**)&doc))) {
    int sub_flag = xmlSubstituteEntitiesDefault(1),
      load_flag = xmlLoadExtDtdDefaultValue;
    xsltStylesheetPtr style;
    xmlLoadExtDtdDefaultValue = 1;
    if (s)
      style = xsltParseStylesheetFile(s);
    else {
      doc = xmlCopyDoc(doc, 1);
      if (!doc) return __ERROR;
      style = xsltParseStylesheetDoc(doc);
      /* FIXME: do we need to free doc here?? */
    }
    xmlSubstituteEntitiesDefault(sub_flag);
    xmlLoadExtDtdDefaultValue = load_flag;
    if (style)
      return mkobj(type(XSLTStylesheet), style);
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xslt_apply_stylesheet,argc,argv)
{
  xsltStylesheetPtr style;
  xmlDocPtr doc;
  expr *xs, *ys;
  int i, j, k, n = 0, m;
  if (argc == 3 && isobj(argv[0], type(XSLTStylesheet), (void**)&style) &&
      isobj(argv[1], type(XMLDoc), (void**)&doc) &&
      (isvoid(argv[2]) || istuple(argv[2], &n, &xs))) {
    char *key, *val;
    const char **params;
    xmlDocPtr res;
    if (n == 2 && isstr(xs[0], &key) && isstr(xs[1], &val)) {
      /* singleton (KEY,VAL) pair */
      n = 1;
      params = malloc((2*n+1)*sizeof(char*));
      if (!params) return __ERROR;
      k = 0;
      params[k++] = key;
      params[k++] = val;
    } else {
      params = malloc((2*n+1)*sizeof(char*));
      if (!params) return __ERROR;
      for (i = k = 0; i < n; i++)
	if (istuple(xs[i], &m, &ys) && m==2 &&
	    isstr(ys[0], &key) && isstr(ys[1], &val)) {
	  params[k++] = key;
	  params[k++] = val;
	} else {
	  free(params);
	  return __FAIL;
	}
    }
    params[k++] = NULL;
    res = xsltApplyStylesheet(style, doc, params);
    free(params);
    if (res) {
      res->_private = mkobj(type(XMLDoc), res);
      return res->_private;
    } else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xslt_save_result_file,argc,argv)
{
  char *s;
  xmlDocPtr doc;
  xsltStylesheetPtr style;
  long n = 0;
  if (argc == 4 && isstr(argv[0], &s) &&
      isobj(argv[1], type(XMLDoc), (void**)&doc) &&
      isobj(argv[2], type(XSLTStylesheet), (void**)&style) &&
      (isvoid(argv[3]) || isint(argv[3], &n))) {
    int res;
    res = xsltSaveResultToFilename(s, doc, style, n);
    if (res >= 0)
      return mkvoid;
    else
      return __FAIL;
  } else
    return __FAIL;
}

FUNCTION(xml,xslt_save_result_string,argc,argv)
{
  xmlDocPtr doc;
  xsltStylesheetPtr style;
  if (argc == 2 && isobj(argv[0], type(XMLDoc), (void**)&doc) &&
      isobj(argv[1], type(XSLTStylesheet), (void**)&style)) {
#ifdef HAVE_XSLTSAVERESULTTOSTRING
    xmlChar *s = NULL;
    int len;
    xsltSaveResultToString(&s, &len, doc, style);
#else
#define BUFSZ 1024
    char *name = tmpnam(NULL);
    int res = xsltSaveResultToFilename(name, doc, style, 0);
    FILE *fp = fopen(name, "r");
    char *s, *t;
    int a;
    if (!fp) goto error;
    s = malloc(BUFSZ); t = s;
    a = BUFSZ;
    if (!s) goto error;
    *s = 0;
    while (fgets(t, BUFSZ, fp)) {
      /* try to enlarge the buffer: */
      int l = strlen(t), k = t-s+l;
      char *s1;
      if (s1 = (char*) realloc(s, a+BUFSZ)) {
	s = s1;
	t = s+k;
	a += BUFSZ;
      } else
	goto error;
    }
    if ((t = realloc(s, strlen(s)+1)))
      s = t;
    goto done;
  error:
    if (s) free(s);
    s = NULL;
  done:
    if (fp) fclose(fp);
    unlink(name);
#endif
    if (s)
      return mkstr(s);
    else
      return __FAIL;
  } else
    return __FAIL;
}


syntax highlighted by Code2HTML, v. 0.9.1