/*
 * tclxslt.c --
 *
 *  Interface to Gnome libxslt.
 *
 * Copyright (c) 2005-2007 Explain
 * http://www.explain.com.au/
 * Copyright (c) 2001-2004 Zveno Pty Ltd
 * http://www.zveno.com/
 *
 * See the file "LICENSE" for information on usage and
 * redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * $Id: tclxslt-libxslt.c,v 1.2 2016/01/15 21:06:01 joye Exp $
 *
 */

#include <tclxslt/tclxslt.h>
#include <string.h>
#include <libxslt/imports.h>
#include <libxslt/security.h>

#undef TCL_STORAGE_CLASS
#define TCL_STORAGE_CLASS DLLEXPORT

#ifdef __WIN32__
/*#     include "win/win32config.h"*/
#endif

/*
 * Manage stylesheet objects
 */

typedef struct TclXSLT_Stylesheet {
  Tcl_Interp *interp;
  char *name;
  xsltStylesheetPtr stylesheet;
  Tcl_HashEntry *entryPtr;

  Tcl_Obj *resulturi;
  Tcl_Obj *profilechannelObj;

  Tcl_Obj *messagecommand;
} TclXSLT_Stylesheet;

/*
 * Extension management
 */

typedef struct TclXSLT_Extension {
  Tcl_Interp *interp;
  Tcl_Obj *nsuri;
  Tcl_Obj *tclns;
  xsltTransformContextPtr xformCtxt;
} TclXSLT_Extension;

typedef struct ThreadSpecificData {
  int initialised;
  Tcl_Interp *interp;
  int ssheetCntr;
  Tcl_HashTable *stylesheets;
  Tcl_HashTable *extensions;
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * Prototypes for procedures defined later in this file:
 */

/*
 * Forward declarations for private functions.
 */

static void TclXSLTGenericError _ANSI_ARGS_((void *ctx, const char *msg, ...));

static int TclXSLTCompileCommand _ANSI_ARGS_((ClientData dummy,
						Tcl_Interp *interp,
						int objc,
						Tcl_Obj *CONST objv[]));
static int TclXSLTInstanceCommand _ANSI_ARGS_((ClientData ssheet,
						Tcl_Interp *interp,
						int objc,
						Tcl_Obj *CONST objv[]));
static void TclXSLTDeleteStylesheet _ANSI_ARGS_((ClientData ssheet));
static int TclXSLTExtensionCommand _ANSI_ARGS_((ClientData dummy,
						Tcl_Interp *interp,
						int objc,
						Tcl_Obj *CONST objv[]));

static Tcl_Obj * GetParameters _ANSI_ARGS_((Tcl_Interp *interp,
					    xsltStylesheetPtr stylesheet));
static int TclXSLTTransform _ANSI_ARGS_((TclXSLT_Stylesheet *stylesheet,
                                         Tcl_Obj *source,
                                         int paramc,
                                         Tcl_Obj *CONST paramv[]));

static void TclXSLT_RegisterAll _ANSI_ARGS_((TclXSLT_Extension *extinfo,
						const xmlChar *nsuri));

/* static xsltExtInitFunction TclXSLTExtInit; */
static void *TclXSLTExtInit _ANSI_ARGS_((xsltTransformContextPtr ctxt,
					const xmlChar *URI));
/* static xsltExtShutdownFunction TclXSLTExtShutdown; */
static void TclXSLTExtShutdown _ANSI_ARGS_((xsltTransformContextPtr ctxt,
					    const xmlChar *URI,
					    void *userdata));
/* static xmlXPathEvalFunc TclXSLTExtFunction; */
static void TclXSLTExtFunction _ANSI_ARGS_((xmlXPathParserContextPtr xpathCtxt,
					    int nargs));
/* static xsltPreComputeFunction TclXSLTExtElementPreComp; */
static void TclXSLTExtElementPreComp _ANSI_ARGS_((xsltStylesheetPtr style,
						  xmlNodePtr inst,
						  xsltTransformFunction function));
/* static xsltTransformFunction TclXSLTExtElementTransform; */
static void TclXSLTExtElementTransform _ANSI_ARGS_((xsltTransformContextPtr ctxt,
					            xmlNodePtr node,
					            xmlNodePtr inst,
					            xsltStylePreCompPtr comp));
/* static xsltSecurityCheck TclXSLTSecurityReadFile; */
static int TclXSLTSecurityReadFile _ANSI_ARGS_((xsltSecurityPrefsPtr sec,
						xsltTransformContextPtr ctxt,
						const char *value));
/* static xsltSecurityCheck TclXSLTSecurityWriteFile; */
static int TclXSLTSecurityWriteFile _ANSI_ARGS_((xsltSecurityPrefsPtr sec,
						 xsltTransformContextPtr ctxt,
						 const char *value));
/* static xsltSecurityCheck TclXSLTSecurityCreateDirectory; */
static int TclXSLTSecurityCreateDirectory _ANSI_ARGS_((xsltSecurityPrefsPtr sec,
						       xsltTransformContextPtr ctxt,
						       const char *value));
/* static xsltSecurityCheck TclXSLTSecurityReadNetwork; */
static int TclXSLTSecurityReadNetwork _ANSI_ARGS_((xsltSecurityPrefsPtr sec,
						   xsltTransformContextPtr ctxt,
						   const char *value));
/* static xsltSecurityCheck TclXSLTSecurityWriteNetwork; */
static int TclXSLTSecurityWriteNetwork _ANSI_ARGS_((xsltSecurityPrefsPtr sec,
						    xsltTransformContextPtr ctxt,
						    const char *value));

static Tcl_Obj * TclXSLT_ConvertXPathObjToTclObj _ANSI_ARGS_((Tcl_Interp *interp,
                                                              xmlXPathObjectPtr xpobj));
static xmlXPathObjectPtr TclXSLT_ConvertTclObjToXPathObj _ANSI_ARGS_((Tcl_Interp *interp,
                                                              Tcl_Obj *objPtr));

/*
 * Error context for passing error result back to caller.
 */

typedef struct GenericError_Info {
  Tcl_Interp *interp;
  TclXSLT_Stylesheet *stylesheet;
  int code;
  Tcl_Obj *msg;
} GenericError_Info;

/*
 * Switch tables
 */

#ifndef CONST84
#define CONST84 /* Before 8.4 no 'const' required */
#endif

static CONST84 char *instanceCommandMethods[] = {
  "cget",
  "configure",
  "get",
  "transform",
  (char *) NULL
};
enum instanceCommandMethods {
  TCLXSLT_CGET,
  TCLXSLT_CONFIGURE,
  TCLXSLT_GET,
  TCLXSLT_TRANSFORM
};
static CONST84 char *instanceCommandOptions[] = {
  "-messagecommand",
  "-method",
  "-indent",
  "-resulturi",
  "-profilechannel",
  "-encoding",
  "-omitxmldeclaration",
  (char *) NULL
};
enum instanceCommandOptions {
  TCLXSLT_OPTION_MESSAGECOMMAND,
  TCLXSLT_OPTION_METHOD,
  TCLXSLT_OPTION_INDENT,
  TCLXSLT_OPTION_RESULTURI,
  TCLXSLT_OPTION_PROFILECHANNEL,
  TCLXSLT_OPTION_ENCODING,
  TCLXSLT_OPTION_OMITXMLDECLARATION
};

static CONST84 char *instanceGetMethods[] = {
  "parameters",
  (char *) NULL
};
enum instanceGetMethods {
  TCLXSLT_GET_PARAMETERS
};

static CONST84 char *extensionCommandMethods[] = {
  "add",
  "remove",
  (char *) NULL
};
enum extensionCommandMethods {
  TCLXSLT_EXT_ADD,
  TCLXSLT_EXT_REMOVE
};

/*
 * libxml2 and libxslt are mostly thread-safe,
 * but there are issues with error callbacks.
 */

TCL_DECLARE_MUTEX(libxslt)

/*
 *----------------------------------------------------------------------------
 *
 * Tclxslt_libxslt_Init --
 *
 *  Initialisation routine for loadable module
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  Creates commands in the interpreter,
 *
 *----------------------------------------------------------------------------
 */

int
Tclxslt_libxslt_Init (interp)
     Tcl_Interp *interp;	/* Interpreter to initialise */
{
  ThreadSpecificData *tsdPtr;
  xsltSecurityPrefsPtr sec;

  tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  if (!tsdPtr->initialised) {
    tsdPtr->initialised = 1;
    tsdPtr->interp = interp;
    tsdPtr->ssheetCntr = 0;
    tsdPtr->stylesheets = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable));
    Tcl_InitHashTable(tsdPtr->stylesheets, TCL_ONE_WORD_KEYS);
    tsdPtr->extensions = (Tcl_HashTable *) Tcl_Alloc(sizeof(Tcl_HashTable));
    Tcl_InitHashTable(tsdPtr->extensions, TCL_STRING_KEYS);
  } /* only need to init the library once per process */

  Tcl_CreateObjCommand(interp, "xslt::compile", TclXSLTCompileCommand, NULL, NULL);
  Tcl_CreateObjCommand(interp, "xslt::extension", TclXSLTExtensionCommand, NULL, NULL);

  Tcl_MutexLock(&libxslt);
#ifndef TCLXML_STATIC_TCLXSLT
  exsltRegisterAll();
#endif /* TCLXML_STATIC_TCLXSLT */

  /*
   * Setup security preferences
   */
  sec = xsltNewSecurityPrefs();
  if (xsltSetSecurityPrefs(sec, XSLT_SECPREF_READ_FILE,
			   TclXSLTSecurityReadFile)) {
    Tcl_SetResult(interp, "unable to set readfile security", NULL);
    return TCL_ERROR;
  }
  if (xsltSetSecurityPrefs(sec, XSLT_SECPREF_WRITE_FILE,
			   TclXSLTSecurityWriteFile)) {
    Tcl_SetResult(interp, "unable to set writefile security", NULL);
    return TCL_ERROR;
  }
  if (xsltSetSecurityPrefs(sec, XSLT_SECPREF_CREATE_DIRECTORY,
			   TclXSLTSecurityCreateDirectory)) {
    Tcl_SetResult(interp, "unable to set createdirectory security", NULL);
    return TCL_ERROR;
  }
  if (xsltSetSecurityPrefs(sec, XSLT_SECPREF_READ_NETWORK,
			   TclXSLTSecurityReadNetwork)) {
    Tcl_SetResult(interp, "unable to set readnetwork security", NULL);
    return TCL_ERROR;
  }
  if (xsltSetSecurityPrefs(sec, XSLT_SECPREF_WRITE_NETWORK,
			   TclXSLTSecurityWriteNetwork)) {
    Tcl_SetResult(interp, "unable to set writenetwork security", NULL);
    return TCL_ERROR;
  }
  /* xsltSetCtxtSecurityPrefs(sec, userCtxt); */
  xsltSetDefaultSecurityPrefs(sec);

  Tcl_MutexUnlock(&libxslt);

  Tcl_SetVar2Ex(interp, "::xslt::libxsltversion", NULL, Tcl_NewStringObj(xsltEngineVersion, -1), 0);
  Tcl_SetVar2Ex(interp, "::xslt::libexsltversion", NULL, Tcl_NewStringObj(exsltLibraryVersion, -1), 0);

  return TCL_OK;
}

/*
 * XSLT is not safe due to the document(), xsl:include and xsl:import functions/elements.
 * However, libxslt checks whether access is permitted to external resources.
 *
 * NOTE: need to make sure decision to allow access to resources is made by a trusted interpreter, not the untrusted slave.  Even better, use a mechanism similar to TclXML/libxml2 to access external resources.
 */

int
Tclxslt_libxslt_SafeInit (interp)
     Tcl_Interp *interp;	/* Interpreter to initialise */
{
  return Tclxslt_libxslt_Init(interp);
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTCompileCommand --
 *
 *  Class creation command for xslt stylesheet objects.
 *
 * Results:
 *  Compiles the XSLT stylesheet.
 *  Creates a Tcl command associated with that stylesheet.
 *
 * Side effects:
 *  Memory allocated, stylesheet is compiled.
 *
 *----------------------------------------------------------------------------
 */

static int
TclXSLTCompileCommand(dummy, interp, objc, objv)
     ClientData dummy;
     Tcl_Interp *interp;
     int objc;
     Tcl_Obj *CONST objv[];
{
  ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  TclXSLT_Stylesheet *info;
  xmlDocPtr origDoc, doc;
  xsltStylesheetPtr ssheetPtr = NULL;
  void *oldxsltErrorCtx, *oldxmlErrorCtx;
  xmlGenericErrorFunc old_xsltGenericError, old_xmlGenericError;
  GenericError_Info *errorInfoPtr;
  Tcl_Obj *errObjPtr = NULL;
  int new;

  if (objc != 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "stylesheet-doc");
    return TCL_ERROR;
  }

  if (TclXML_libxml2_GetDocFromObj(interp, objv[1], &origDoc) != TCL_OK) {
    return TCL_ERROR;
  }

  Tcl_MutexLock(&libxslt);
  doc = xmlCopyDoc(origDoc, 1);
  /*
   * xmlCopyDoc doesn't copy some of the fields.
   */
  if (origDoc->URL) {
    doc->URL = (const xmlChar *) Tcl_Alloc(strlen((char *) origDoc->URL) + 1);
    strcpy((char *) doc->URL, (char *) origDoc->URL);
  }

  /*
   * Prepare for compiling stylesheet
   */

  TclXML_libxml2_ResetError(interp);

  errorInfoPtr = (GenericError_Info *) Tcl_Alloc(sizeof(GenericError_Info));
  errorInfoPtr->interp = interp;
  errorInfoPtr->stylesheet = NULL;
  errorInfoPtr->code = TCL_OK;
  errorInfoPtr->msg = NULL;
  xmlSetGenericErrorFunc((void *) errorInfoPtr,
			 TclXSLTGenericError);

  /*
   * Save the previous error context so that it can
   * be restored upon completion of the operation.
   */
  old_xsltGenericError = xsltGenericError;
  oldxsltErrorCtx = xsltGenericErrorContext;
  old_xmlGenericError = xmlGenericError;
  oldxmlErrorCtx = xmlGenericErrorContext;

  xmlSetGenericErrorFunc((void *) errorInfoPtr,
			 TclXSLTGenericError);
  xsltSetGenericErrorFunc((void *) errorInfoPtr, TclXSLTGenericError);

  /*
   * Compile stylesheet
   */

  ssheetPtr = xsltParseStylesheetDoc(doc);

  xmlSetGenericErrorFunc((void *) oldxmlErrorCtx, old_xmlGenericError);
  xsltSetGenericErrorFunc((void *) oldxsltErrorCtx, old_xsltGenericError);

  Tcl_MutexUnlock(&libxslt);

  errObjPtr = TclXML_libxml2_GetErrorObj(interp);

  if (ssheetPtr == NULL) {
    Tcl_SetResult(interp, "error compiling stylesheet", NULL);
    goto error;
  }

  if (ssheetPtr->errors > 0) {
    Tcl_SetResult(interp, "error compiling XSLT stylesheet", NULL);
    goto error;
  }

  if (errorInfoPtr->code != TCL_OK) {
    goto error;
  }

  /* TODO: notify app of any warnings */

  info = (TclXSLT_Stylesheet *) Tcl_Alloc(sizeof(TclXSLT_Stylesheet));
  info->interp = interp;
  info->name = Tcl_Alloc(20);
  sprintf(info->name, "style%d", tsdPtr->ssheetCntr++);
  info->stylesheet = ssheetPtr;
  info->messagecommand = NULL;
  info->resulturi = NULL;
  info->profilechannelObj = NULL;

  /*
   * Create reverse mapping of stylesheet to name of stylesheet command.
   */
  info->entryPtr = Tcl_CreateHashEntry(tsdPtr->stylesheets, (ClientData) ssheetPtr, &new);
  /* sanity check: new == 1 */
  Tcl_SetHashValue(info->entryPtr, (ClientData) info->name);

  Tcl_CreateObjCommand(interp, info->name, TclXSLTInstanceCommand, (ClientData) info, TclXSLTDeleteStylesheet);

  Tcl_SetObjResult(interp, Tcl_NewStringObj(info->name, -1));

  return TCL_OK;

error:

  if (errObjPtr) {
    Tcl_SetObjResult(interp, errObjPtr);
  } else if (errorInfoPtr->msg) {
    Tcl_SetObjResult(interp, errorInfoPtr->msg);
    Tcl_DecrRefCount(errorInfoPtr->msg);
  }
  Tcl_Free((char *) errorInfoPtr);

  Tcl_MutexLock(&libxslt);
  if (ssheetPtr) {
    xsltFreeStylesheet(ssheetPtr);
  } else {
    xmlFreeDoc(doc);
  }
  Tcl_MutexUnlock(&libxslt);
  
  return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTDeleteStylesheet --
 *
 *  Class destruction command for xslt stylesheet objects.
 *
 * Results:
 *  Frees memory associated with a stylesheet.
 *
 * Side effects:
 *  Memory deallocated.
 *
 *----------------------------------------------------------------------------
 */

static void
TclXSLTDeleteStylesheet(clientData)
     ClientData clientData;
{
  TclXSLT_Stylesheet *ssheet = (TclXSLT_Stylesheet *) clientData;

  Tcl_DeleteHashEntry(ssheet->entryPtr);
  
  Tcl_Free(ssheet->name);
  if (ssheet->messagecommand) {
    Tcl_DecrRefCount(ssheet->messagecommand);
  }
  if (ssheet->resulturi) {
    Tcl_DecrRefCount(ssheet->resulturi);
  }
  if (ssheet->profilechannelObj) {
    Tcl_DecrRefCount(ssheet->profilechannelObj);
  }
  Tcl_MutexLock(&libxslt);
  xsltFreeStylesheet(ssheet->stylesheet); /* Also frees document */
  Tcl_MutexUnlock(&libxslt);
  Tcl_Free((char *) ssheet);
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTInstanceCommand --
 *
 *  Handles the stylesheet object command.
 *
 * Results:
 *  Depends on method.
 *
 * Side effects:
 *  Depends on method.
 *
 *----------------------------------------------------------------------------
 */

static int
TclXSLTInstanceCommand(clientData, interp, objc, objv)
     ClientData clientData;
     Tcl_Interp *interp;
     int objc;
     Tcl_Obj *CONST objv[];
{
  TclXSLT_Stylesheet *ssheet = (TclXSLT_Stylesheet *) clientData;
  int method, option, indent = 0, theOmitXMLDeclaration = 0;
  const xmlChar *theMethod, *theEncoding;

  if (objc < 3) {
    Tcl_WrongNumArgs(interp, 1, objv, "method ?args ...?");
    return TCL_ERROR;
  }

  if (Tcl_GetIndexFromObj(interp, objv[1], instanceCommandMethods, 
			    "method", 0, &method) != TCL_OK) {
    return TCL_ERROR;
  }

  switch ((enum instanceCommandMethods) method) {
  case TCLXSLT_CGET:

    if (objc != 3) {
      Tcl_WrongNumArgs(interp, 2, objv, "option");
      return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], instanceCommandOptions, 
			    "option", 0, &option) != TCL_OK) {
      return TCL_ERROR;
    }

    switch ((enum instanceCommandOptions) option) {

    case TCLXSLT_OPTION_METHOD:
      XSLT_GET_IMPORT_PTR(theMethod, ssheet->stylesheet, method);
      if (theMethod != NULL) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj((CONST char *) theMethod, -1));
      } /* theMethod == NULL means XML method; result should be empty.
	   EXCEPTION: if the result document is of type XML_HTML_DOCUMENT_NODE
	   then the method should be "html".
	 */
      break;

    case TCLXSLT_OPTION_ENCODING:
      XSLT_GET_IMPORT_PTR(theEncoding, ssheet->stylesheet, encoding);
      if (theEncoding != NULL) {
        Tcl_SetObjResult(interp, Tcl_NewStringObj((CONST char *) theEncoding, -1));
      } /* theEncoding == NULL means default (UTF-8) encoding; result should be empty.
	 */
      break;

    case TCLXSLT_OPTION_OMITXMLDECLARATION:
      XSLT_GET_IMPORT_INT(theOmitXMLDeclaration, ssheet->stylesheet, omitXmlDeclaration);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(theOmitXMLDeclaration == 1));
      break;

    case TCLXSLT_OPTION_INDENT:
      XSLT_GET_IMPORT_INT(indent, ssheet->stylesheet, indent);
      Tcl_SetObjResult(interp, Tcl_NewBooleanObj(indent));
      break;

    case TCLXSLT_OPTION_MESSAGECOMMAND:
      if (ssheet->messagecommand != NULL) {
        Tcl_SetObjResult(interp, ssheet->messagecommand);
      }
      break;

    case TCLXSLT_OPTION_RESULTURI:
      if (ssheet->resulturi != NULL) {
        Tcl_SetObjResult(interp, ssheet->resulturi);
      }
      break;

    case TCLXSLT_OPTION_PROFILECHANNEL:
      if (ssheet->profilechannelObj != NULL) {
        Tcl_SetObjResult(interp, ssheet->profilechannelObj);
      }
      break;

    default:
      Tcl_SetResult(interp, "unknown option", NULL);
      return TCL_ERROR;
    }

    break;

  case TCLXSLT_CONFIGURE:
    
    if (objc != 4) {
      Tcl_WrongNumArgs(interp, 2, objv, "option value");
      return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], instanceCommandOptions, 
			    "option", 0, &option) != TCL_OK) {
      return TCL_ERROR;
    }

    switch ((enum instanceCommandOptions) option) {

    case TCLXSLT_OPTION_METHOD:
    case TCLXSLT_OPTION_INDENT:
    case TCLXSLT_OPTION_ENCODING:
    case TCLXSLT_OPTION_OMITXMLDECLARATION:
      Tcl_SetResult(interp, "read-only option", NULL);
      return TCL_ERROR;
      break;

    case TCLXSLT_OPTION_MESSAGECOMMAND:
      if (ssheet->messagecommand != NULL) {
        Tcl_DecrRefCount(ssheet->messagecommand);
      }
      ssheet->messagecommand = objv[3];
      Tcl_IncrRefCount(ssheet->messagecommand);
      break;

    case TCLXSLT_OPTION_RESULTURI:
      if (ssheet->resulturi != NULL) {
        Tcl_DecrRefCount(ssheet->resulturi);
      }
      ssheet->resulturi = objv[3];
      Tcl_IncrRefCount(ssheet->resulturi);
      break;

    case TCLXSLT_OPTION_PROFILECHANNEL:
      if (ssheet->profilechannelObj != NULL) {
        Tcl_DecrRefCount(ssheet->profilechannelObj);
      }
#ifdef __WIN32__
      Tcl_SetResult(interp, "profiling not available", NULL);
      return TCL_ERROR;
#else
      ssheet->profilechannelObj = objv[3];
      Tcl_IncrRefCount(ssheet->profilechannelObj);
#endif
      break;

    default:
      Tcl_SetResult(interp, "unknown option", NULL);
      return TCL_ERROR;
    }

    break;

  case TCLXSLT_GET:
    if (objc != 3) {
      Tcl_WrongNumArgs(interp, 2, objv, "name");
      return TCL_ERROR;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], instanceGetMethods,
			    "name", 0, &option) != TCL_OK) {
      return TCL_ERROR;
    }

    switch ((enum instanceGetMethods) option) {
    case TCLXSLT_GET_PARAMETERS:

      Tcl_SetObjResult(interp, GetParameters(interp, ssheet->stylesheet));
      break;

    default:
      Tcl_SetResult(interp, "unknown name", NULL);
      return TCL_ERROR;
    }

    break;

  case TCLXSLT_TRANSFORM:
    if (objc < 3) {
      Tcl_WrongNumArgs(interp, 2, objv, "source ?param value...?");
      return TCL_ERROR;
    }

    return TclXSLTTransform(ssheet, objv[2], objc - 3, &objv[3]);

    break;

  default:
    Tcl_SetResult(interp, "unknown method", NULL);
    return TCL_OK;
  }

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTTransform --
 *
 *  Performs an XSL transformation.
 *
 * Results:
 *  Result document created.
 *
 * Side effects:
 *  Memory allocated for result document.
 *
 *----------------------------------------------------------------------------
 */

static int
TclXSLTTransform(stylesheet, source, paramc, paramv)
    TclXSLT_Stylesheet *stylesheet;
    Tcl_Obj *source;
    int paramc;
    Tcl_Obj *CONST paramv[];
{
  xmlDocPtr doc, result;
  char **params = NULL;
  int nbparams = 0, i;
  GenericError_Info *errorInfoPtr;
  void *oldxsltErrorCtx, *oldxmlErrorCtx;
  xmlGenericErrorFunc old_xsltGenericError, old_xmlGenericError;
  Tcl_Obj *resultObjPtr, *errObjPtr = NULL;
  char *resulturi = NULL;
  FILE *profile = NULL;
  xsltTransformContextPtr userCtxt = NULL;

  errorInfoPtr = (GenericError_Info *) Tcl_Alloc(sizeof(GenericError_Info));
  errorInfoPtr->interp = stylesheet->interp;
  errorInfoPtr->stylesheet = stylesheet;
  errorInfoPtr->code = TCL_OK;
  errorInfoPtr->msg = NULL;

  if (TclXML_libxml2_GetDocFromObj(stylesheet->interp, source, &doc) != TCL_OK) {
    goto error;
  }

  TclXML_libxml2_ResetError(stylesheet->interp);

  params = (char **) Tcl_Alloc(sizeof(char **) * (paramc + 1));
  for (i = 0; i < paramc; i++) {
    params[nbparams++] = Tcl_GetStringFromObj(paramv[i++], NULL);
    params[nbparams++] = Tcl_GetStringFromObj(paramv[i], NULL);
  }
  params[nbparams] = NULL;

  if (stylesheet->resulturi) {
    resulturi = Tcl_GetStringFromObj(stylesheet->resulturi, NULL);
  }
#ifdef __WIN32__
  /* Tcl_GetOpenFile not available on Windows */
#else
  if (stylesheet->profilechannelObj) {
    if (Tcl_GetOpenFile(stylesheet->interp,
			Tcl_GetStringFromObj(stylesheet->profilechannelObj, NULL),
			1, 1,
			(ClientData *) &profile) != TCL_OK) {
      goto error;
    }
  }
#endif

  /*
   * Perform the transformation
   */

  Tcl_MutexLock(&libxslt);

  /*
   * Save the previous error context so that it can
   * be restored upon completion of the transformation.
   * This is necessary because transformations may occur
   * recursively (usually due to extensions).
   */
  old_xsltGenericError = xsltGenericError;
  oldxsltErrorCtx = xsltGenericErrorContext;
  old_xmlGenericError = xmlGenericError;
  oldxmlErrorCtx = xmlGenericErrorContext;

  xmlSetGenericErrorFunc((void *) errorInfoPtr,
			 TclXSLTGenericError);
  xsltSetGenericErrorFunc((void *) errorInfoPtr, TclXSLTGenericError);

  userCtxt = xsltNewTransformContext(stylesheet->stylesheet, doc);
  if (userCtxt == NULL) {
    xmlSetGenericErrorFunc((void *) oldxmlErrorCtx, old_xmlGenericError);
    xsltSetGenericErrorFunc((void *) oldxsltErrorCtx, old_xsltGenericError);

    Tcl_MutexUnlock(&libxslt);
    Tcl_SetResult(stylesheet->interp, "unable to create transformation context", NULL);
    goto error;
  }

  result = xsltApplyStylesheetUser(stylesheet->stylesheet,
				   doc,
				   (const char **)params,
				   resulturi,
				   profile,
				   userCtxt);

  xsltFreeTransformContext(userCtxt);

  xmlSetGenericErrorFunc((void *) oldxmlErrorCtx, old_xmlGenericError);
  xsltSetGenericErrorFunc((void *) oldxsltErrorCtx, old_xsltGenericError);

  Tcl_MutexUnlock(&libxslt);

  errObjPtr = TclXML_libxml2_GetErrorObj(stylesheet->interp);

  if (result == NULL) {
    Tcl_Obj *resultPtr = Tcl_NewStringObj("no result document: ", -1);

    if (errObjPtr) {
      Tcl_AppendObjToObj(resultPtr, errObjPtr);
      Tcl_SetObjResult(stylesheet->interp, resultPtr);
      goto error;
    } else {
      if (errorInfoPtr->msg) {
	Tcl_AppendObjToObj(resultPtr, errorInfoPtr->msg);
      }

      Tcl_SetObjResult(stylesheet->interp, resultPtr);
      goto error;
    }
  }

  if ((errObjPtr || (errorInfoPtr->code != TCL_OK && errorInfoPtr->msg)) && stylesheet->messagecommand) {

    /* We have produced a result, but there may possibly
     * have been errors.  Trouble is, there might also
     * have been some completely innocent messages.
     * -messageCommand is the only way to find out about these.
     */

    Tcl_Obj *cmdPtr = Tcl_DuplicateObj(stylesheet->messagecommand);
    if (errObjPtr) {
      if (Tcl_ListObjAppendElement(stylesheet->interp, cmdPtr, errObjPtr) != TCL_OK) {
	goto error;
      }
    } else {
      if (Tcl_ListObjAppendElement(stylesheet->interp, cmdPtr, errorInfoPtr->msg) != TCL_OK) {
	goto error;
      }
    }
    if (Tcl_GlobalEvalObj(stylesheet->interp, cmdPtr) != TCL_OK) {
      Tcl_Obj *resultPtr = Tcl_NewStringObj("message command failed: ", -1);

      Tcl_AppendObjToObj(resultPtr, Tcl_GetObjResult(stylesheet->interp));
      Tcl_SetObjResult(stylesheet->interp, resultPtr);
      goto error;
    }

  }

  resultObjPtr = TclDOM_libxml2_CreateObjFromDoc(stylesheet->interp, result);
  Tcl_SetObjResult(stylesheet->interp, resultObjPtr);

  if (errorInfoPtr->msg) {
    Tcl_DecrRefCount(errorInfoPtr->msg);
  }
  Tcl_Free((char *) errorInfoPtr);
  Tcl_Free((char *) params);

  return TCL_OK;

 error:

  if (errorInfoPtr->msg) {
    Tcl_DecrRefCount(errorInfoPtr->msg);
  }
  if (params) {
    Tcl_Free((char *) params);
  }
  Tcl_Free((char *) errorInfoPtr);

  return TCL_ERROR;
}

void
ListObjAppendUniqueList(interp, tablePtr, listPtr, newElementsPtr)
     Tcl_Interp *interp;
     Tcl_HashTable *tablePtr;
     Tcl_Obj *listPtr;
     Tcl_Obj *newElementsPtr;
{
  int len, idx;
  Tcl_Obj *elementPtr, *keyPtr, *namePtr, *nameURIPtr;
  Tcl_HashEntry *entryPtr;

  Tcl_ListObjLength(interp, newElementsPtr, &len);
  for (idx = 0; idx < len; idx++) {
	Tcl_ListObjIndex(interp, newElementsPtr, idx, &elementPtr);
	Tcl_ListObjIndex(interp, elementPtr, 0, &namePtr);
	Tcl_ListObjIndex(interp, elementPtr, 1, &nameURIPtr);

	keyPtr = Tcl_NewObj();
	Tcl_AppendStringsToObj(keyPtr,
						   Tcl_GetStringFromObj(nameURIPtr, NULL), 
						   "^", 
						   Tcl_GetStringFromObj(namePtr, NULL),
						   NULL);
	entryPtr = Tcl_FindHashEntry(tablePtr, (CONST char *) keyPtr);
	if (entryPtr == NULL) {
	  Tcl_ListObjAppendElement(interp, listPtr, elementPtr);
	}
	Tcl_DecrRefCount(keyPtr);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * GetParameters --
 *
 *  Retrieves the parameters for a stylesheet.
 *
 * Results:
 *  Returns a Tcl list object.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------------
 */

static Tcl_Obj *
GetParameters(interp, stylesheet)
     Tcl_Interp *interp;
     xsltStylesheetPtr stylesheet;
{
  Tcl_Obj *resultPtr, *objPtr, *keyPtr;
  xsltStackElemPtr varPtr;
  Tcl_HashTable entries;  /* to keep track of parameter qnames */
  int new;

  if (stylesheet == NULL) {
    return NULL;
  }

  resultPtr = Tcl_NewListObj(0, NULL);
  Tcl_InitObjHashTable(&entries);

  for (varPtr = stylesheet->variables; varPtr; varPtr = varPtr->next) {
    Tcl_Obj *listPtr;

    if (strcmp((char *) varPtr->comp->inst->name, "param") == 0) {
      listPtr = Tcl_NewListObj(0, NULL);
      Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj((CONST char *) varPtr->name, -1));
      Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj((CONST char *) varPtr->nameURI, -1));
      Tcl_ListObjAppendElement(interp, listPtr, Tcl_NewStringObj((CONST char *) varPtr->select, -1));

      Tcl_ListObjAppendElement(interp, resultPtr, listPtr);

	  keyPtr = Tcl_NewStringObj((CONST char *) varPtr->nameURI, -1);
	  Tcl_AppendStringsToObj(keyPtr, "^", varPtr->name, NULL);
	  Tcl_CreateHashEntry(&entries, (CONST char *) keyPtr, &new);
    }
  }

  objPtr = GetParameters(interp, stylesheet->next);
  if (objPtr) {
    ListObjAppendUniqueList(interp, &entries, resultPtr, objPtr);
  }
  objPtr = GetParameters(interp, stylesheet->imports);
  if (objPtr) {
    ListObjAppendUniqueList(interp, &entries, resultPtr, objPtr);
  }

  Tcl_DeleteHashTable(&entries);

  return resultPtr;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTGenericError --
 *
 *  Handler for stylesheet errors.
 *
 *  NB. Cannot distinguish between errors and use of xsl:message element.
 *
 * Results:
 *  Stores error message.
 *
 * Side effects:
 *  Transform will return error condition.
 *
 *----------------------------------------------------------------------------
 */

static void
TclXSLTGenericError (void *ctx, const char *msg, ...)
{
  va_list args;
  char buf[2048];
  int len;
  GenericError_Info *errorInfoPtr = (GenericError_Info *) ctx;

  if (ctx < (void *) 0x1000) {
    fprintf(stderr, "TclXSLT: bad context\n");
    va_start(args,msg);
    vfprintf(stderr, msg, args);
    va_end(args);
    return;
  }

  va_start(args,msg);
  len = vsnprintf(buf, 2047, msg, args);
  va_end(args);

  if (!errorInfoPtr->interp) {
    return;
  }

  if (errorInfoPtr->stylesheet && errorInfoPtr->stylesheet->messagecommand) {

    Tcl_Obj *cmdPtr = Tcl_DuplicateObj(errorInfoPtr->stylesheet->messagecommand);
    if (Tcl_ListObjAppendElement(errorInfoPtr->interp, cmdPtr, Tcl_NewStringObj(buf, len)) != TCL_OK) {
      Tcl_BackgroundError(errorInfoPtr->interp);
      return;
    }
    if (Tcl_GlobalEvalObj(errorInfoPtr->interp, cmdPtr) != TCL_OK) {
      Tcl_BackgroundError(errorInfoPtr->interp);
      return;
    }

  } else {

    if (!errorInfoPtr->msg) {
      errorInfoPtr->msg = Tcl_NewObj();
      Tcl_IncrRefCount(errorInfoPtr->msg);
    }

    errorInfoPtr->code = TCL_ERROR;

    Tcl_AppendToObj(errorInfoPtr->msg, buf, len);

  }
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTExtensionCommand --
 *
 *  Command for xslt::extension command.
 *
 * Results:
 *  Depends on method.
 *
 * Side effects:
 *  Depends on method
 *
 *----------------------------------------------------------------------------
 */

static int
TclXSLTExtensionCommand(dummy, interp, objc, objv)
     ClientData dummy;
     Tcl_Interp *interp;
     int objc;
     Tcl_Obj *CONST objv[];
{
  ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  int method, new;
  TclXSLT_Extension *extinfo;
  Tcl_HashEntry *entry;

  if (objc < 2) {
    Tcl_WrongNumArgs(interp, 1, objv, "method ?args ...?");
    return TCL_ERROR;
  }

  if (Tcl_GetIndexFromObj(interp, objv[1], extensionCommandMethods,
			  "method", 0, &method) != TCL_OK) {
    return TCL_ERROR;
  }

  switch ((enum extensionCommandMethods) method) {

  case TCLXSLT_EXT_ADD:
    if (objc != 4) {
      Tcl_WrongNumArgs(interp, 2, objv, "nsuri tcl-namespace");
      return TCL_ERROR;
    }

    Tcl_MutexLock(&libxslt);

    if (xsltRegisterExtModule((const xmlChar *) Tcl_GetStringFromObj(objv[2], NULL),
			      TclXSLTExtInit,
			      TclXSLTExtShutdown)) {
      Tcl_MutexUnlock(&libxslt);
      Tcl_SetResult(interp, "cannot register extension module", NULL);
    }

    Tcl_MutexUnlock(&libxslt);

    extinfo = (TclXSLT_Extension *) Tcl_Alloc(sizeof(TclXSLT_Extension));
    extinfo->interp = interp;
    extinfo->nsuri = objv[2];
    Tcl_IncrRefCount(objv[2]);
    extinfo->tclns = objv[3];
    Tcl_IncrRefCount(objv[3]);

    extinfo->xformCtxt = NULL;

    entry = Tcl_CreateHashEntry(tsdPtr->extensions, Tcl_GetStringFromObj(objv[2], NULL), &new);

    if (!new) {
      Tcl_SetResult(interp, "extension already exists", NULL);
      Tcl_Free((char *) extinfo);
      return TCL_ERROR;
    }

    Tcl_SetHashValue(entry, extinfo);

    TclXSLT_RegisterAll(extinfo, (const xmlChar *) Tcl_GetStringFromObj(objv[2], NULL));

    Tcl_ResetResult(interp);

    break;

  case TCLXSLT_EXT_REMOVE:
    if (objc != 3) {
      Tcl_WrongNumArgs(interp, 2, objv, "nsuri");
      return TCL_ERROR;
    }

    /*
     * TODO: Remove previously registered elements and functions.
    */

    entry = Tcl_FindHashEntry(tsdPtr->extensions, Tcl_GetStringFromObj(objv[2], NULL));
    if (entry == NULL) {
      Tcl_SetResult(interp, "unknown XML Namespace URI", NULL);
      return TCL_ERROR;
    }

    extinfo = (TclXSLT_Extension *) Tcl_GetHashValue(entry);
    Tcl_DecrRefCount(extinfo->nsuri);
    Tcl_DecrRefCount(extinfo->tclns);
    Tcl_Free((char *) extinfo);

    Tcl_DeleteHashEntry(entry);

    break;

  default:
    Tcl_SetResult(interp, "unknown method", NULL);
    return TCL_ERROR;
  }

  return TCL_OK;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTExtInit --
 *
 *  Load extensions into a transformation context.
 *
 * Results:
 *  Returns pointer to extension data.
 *  Elements and functions are pre-registered.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------------
 */

static void *
TclXSLTExtInit(ctxt, URI)
     xsltTransformContextPtr ctxt;
     const xmlChar *URI;
{
  ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  Tcl_HashEntry *entry;
  TclXSLT_Extension *extinfo;

  entry = Tcl_FindHashEntry(tsdPtr->extensions, (CONST char *) URI);
  if (entry == NULL) {
    /* Extension module was removed */
    return NULL;
  }

  extinfo = (TclXSLT_Extension *) Tcl_GetHashValue(entry);
  extinfo->xformCtxt = ctxt;

  return (void *) extinfo;
}

void
TclXSLT_RegisterAll(extinfo, nsuri)
    TclXSLT_Extension *extinfo;
    const xmlChar *nsuri;
{
  Tcl_Obj *cmdPtr, *objPtr;
  Tcl_Obj **reg;
  int ret, i, len;

  /*
   * Q: How to distinguish between extension elements and functions?
   * A: Use the formal parameters.  If the command can accept
   * a variable argument list, then it is registered as a function.
   * Otherwise it will be registered as an extension (and expected
   * to accept certain arguments).
   */

  cmdPtr = Tcl_NewStringObj("::xslt::getprocs ", -1);
  Tcl_IncrRefCount(cmdPtr);
  Tcl_AppendObjToObj(cmdPtr, extinfo->tclns);
  ret = Tcl_EvalObjEx(extinfo->interp, cmdPtr, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);
  objPtr = Tcl_GetObjResult(extinfo->interp);
  Tcl_IncrRefCount(objPtr);
  Tcl_DecrRefCount(cmdPtr);

  if (ret != TCL_OK || objPtr == NULL) {
    /*
     * Something went wrong, therefore nothing to register.
     */
    return;
  }

  ret = Tcl_ListObjGetElements(extinfo->interp, objPtr, &len, &reg);
  if (ret != TCL_OK || len != 2) {
    /*
     * Something went wrong, therefore nothing to register.
     */
    return;
  }

  /*
   * reg[0] contains extension elements
   * reg[1] contains extension functions
   */

  Tcl_MutexLock(&libxslt);

  /*
   * First register the extension elements.
   */

  ret = Tcl_ListObjLength(extinfo->interp, reg[0], &len);
  if (ret == TCL_OK && len > 0) {
    for (i = 0; i < len; i++) {

      if (Tcl_ListObjIndex(extinfo->interp, reg[0], i, &objPtr) != TCL_OK) {
        continue;
      }

      xsltRegisterExtModuleElement((const xmlChar *) Tcl_GetStringFromObj(objPtr, NULL),
                             nsuri,
                             (xsltPreComputeFunction) TclXSLTExtElementPreComp,
                             (xsltTransformFunction) TclXSLTExtElementTransform);
    }
  }

  /*
   * Now register the extension functions.
   */

  ret = Tcl_ListObjLength(extinfo->interp, reg[1], &len);
  if (ret != TCL_OK || len == 0) {
    Tcl_MutexUnlock(&libxslt);
    return;
  }

  for (i = 0; i < len; i++) {

    if (Tcl_ListObjIndex(extinfo->interp, reg[1], i, &objPtr) != TCL_OK) {
      continue;
    }

    xsltRegisterExtModuleFunction((const xmlChar *) Tcl_GetStringFromObj(objPtr, NULL),
    	nsuri,
    	TclXSLTExtFunction);
  }

  Tcl_MutexUnlock(&libxslt);

  Tcl_DecrRefCount(objPtr);

  return;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTExtElementPreComp --
 *
 *  Compilation step for extension element.
 *
 * Results:
 *  Not currently used.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------------
 */

static void 
TclXSLTExtElementPreComp(style, inst, function)
    xsltStylesheetPtr style;
    xmlNodePtr inst;
    xsltTransformFunction function;
{
  return;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTExtElementTransform --
 *
 *  Implements extension element.
 *
 * Results:
 *  Returns string returned by Tcl command evaluation.
 *
 * Side effects:
 *  Depends on Tcl command evaluated.
 *
 *----------------------------------------------------------------------------
 */

static void 
TclXSLTExtElementTransform(ctxt, node, inst, comp)
    xsltTransformContextPtr ctxt; /* unused */
    xmlNodePtr node;
    xmlNodePtr inst;
    xsltStylePreCompPtr comp; /* unused */
{
  ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  TclXSLT_Extension *extinfo;
  Tcl_HashEntry *entry;
  Tcl_Obj *cmdPtr;
  int ret;

  if (inst == NULL) {
    return;
  }

  entry = Tcl_FindHashEntry(tsdPtr->extensions, (CONST char *) inst->ns->href);
  if (entry == NULL) {
    /*
     * Cannot find extension module.
     * Must have been removed.
     */
    return;
  }

  extinfo = (TclXSLT_Extension *) Tcl_GetHashValue(entry);

  /*
   * Start constructing the script by first defining the command.
   */

  cmdPtr = Tcl_DuplicateObj(extinfo->tclns);
  Tcl_AppendStringsToObj(cmdPtr, "::", inst->name, NULL);

  if (Tcl_ListObjAppendElement(extinfo->interp, cmdPtr, TclDOM_libxml2_CreateObjFromNode(extinfo->interp, node)) != TCL_OK) {
    Tcl_DecrRefCount(cmdPtr);
    return;
  }

  /*
   * Converting the stylesheet node to a TclDOM node may clobber the
   * _private pointer.  It would be nice to find the equivalent node
   * in the original DOM tree, but it may not even exist anymore :-(
   *
   * TODO: make extension elements more effective, and allow
   * pre-computation.
   */

  /*
   * Now evaluate the complete command.
   * Can't propagqte a return error result to
   * XSLT, so flag background error instead.
   */
  ret = Tcl_EvalObjEx(extinfo->interp, cmdPtr, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
  if (ret != TCL_OK) {
    Tcl_BackgroundError(extinfo->interp);
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTExtFunction --
 *
 *  Handles evaluation of an extension function.
 *
 * Results:
 *  Returns string returned by Tcl command evaluation.
 *
 * Side effects:
 *  Depends on Tcl command evaluated.
 *
 *----------------------------------------------------------------------------
 */

static void 
TclXSLTExtFunction(xpathCtxt, nargs)
     xmlXPathParserContextPtr xpathCtxt;
     int nargs;
{
  xsltTransformContextPtr xformCtxt;
  TclXSLT_Extension *extinfo;
  Tcl_Obj *cmdPtr, *resultPtr;
  xmlXPathObjectPtr obj;
  int ret;

  Tcl_MutexLock(&libxslt);

  xformCtxt = xsltXPathGetTransformContext(xpathCtxt);

  /*
   * In order to find the instance data we need the
   * XML Namespace URI of this function.
   */

  extinfo = (TclXSLT_Extension *) xsltGetExtData(xformCtxt,
						 xpathCtxt->context->functionURI);

  /*
   * Start constructing the script by first defining the command.
   */

  cmdPtr = Tcl_DuplicateObj(extinfo->tclns);
  Tcl_IncrRefCount(cmdPtr);
  Tcl_AppendStringsToObj(cmdPtr, "::", xpathCtxt->context->function, NULL);

  /*
   * Each argument on the stack is converted to a Tcl_Obj
   * of an appropriate type and passed as an argument to the Tcl command.
   */

  while (nargs) {
    Tcl_Obj *objv[2];

    obj = (xmlXPathObjectPtr) valuePop(xpathCtxt);
    if (obj == NULL) {
      xmlXPathSetError(xpathCtxt, XPATH_INVALID_OPERAND);
      Tcl_DecrRefCount(cmdPtr);
      Tcl_MutexUnlock(&libxslt);
      return;
    }

    objv[0] = TclXSLT_ConvertXPathObjToTclObj(extinfo->interp, obj);
    objv[1] = NULL;
    if (Tcl_ListObjReplace(extinfo->interp, cmdPtr, 1, 0, 1, objv) != TCL_OK) {
      Tcl_BackgroundError(extinfo->interp);
      Tcl_DecrRefCount(objv[0]);
      Tcl_DecrRefCount(cmdPtr);
      Tcl_MutexUnlock(&libxslt);
      return;
    }

    /* When should this XPath object be freed?
     * Immediately before returning from the function call?
     * What if the application retains a pointer to it?
     * If the application destroys the contents, then memory
     * will leak because the XPath object is not freed.
     *
     * TODO: take a copy of the object's content and pass that
     * to the application callback.  That would allow this object
     * to be freed and allow the application to manage the copy.
     
     xmlXPathFreeObject(obj);
     */

    nargs--;
  }

  ret = Tcl_EvalObjEx(extinfo->interp, cmdPtr, TCL_EVAL_GLOBAL | TCL_EVAL_DIRECT);
  resultPtr = Tcl_GetObjResult(extinfo->interp);
  Tcl_DecrRefCount(cmdPtr);
  Tcl_IncrRefCount(resultPtr);

  if (ret == TCL_OK) {
    obj = TclXSLT_ConvertTclObjToXPathObj(extinfo->interp, resultPtr);
    valuePush(xpathCtxt, obj);
  } else {
    xmlGenericError(xmlGenericErrorContext,
		    "%s", Tcl_GetStringFromObj(resultPtr, NULL));
    /* Need to define a new error code - this is the closest in meaning */
    xpathCtxt->error = XPATH_UNKNOWN_FUNC_ERROR;
  }

  Tcl_MutexUnlock(&libxslt);

  Tcl_DecrRefCount(resultPtr);

}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLT_ConvertTclObjToXPathObj --
 *
 *  Convert a Tcl Object to an XPath object.
 *  Data type is preserved, with nodesets being
 *  mapped from a list of nodes.
 *
 * NB. Mutex is assumed to be locked when invoking this routine.
 *
 * Results:
 *  XPath Object.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------------
 */

static xmlXPathObjectPtr
TclXSLT_ConvertTclObjToXPathObj(interp, objPtr)
     Tcl_Interp *interp;
     Tcl_Obj *objPtr;
{
  xmlNodePtr nodePtr;
  xmlDocPtr docPtr;

  if (TclDOM_libxml2_GetNodeFromObj(interp, objPtr, &nodePtr) == TCL_OK) {
    return xmlXPathNewNodeSet(nodePtr);
  }
  
   if (TclXML_libxml2_GetDocFromObj(interp, objPtr, &docPtr) == TCL_OK) {
    return xmlXPathNewNodeSet((xmlNodePtr) docPtr);

  }
  
  if (objPtr->typePtr == Tcl_GetObjType("int") ||
      objPtr->typePtr == Tcl_GetObjType("double")) {
    double number;

    if (Tcl_GetDoubleFromObj(interp, objPtr, &number) == TCL_OK) {
      return xmlXPathNewFloat(number);
    } else {
      return NULL;
    }
  } else if (objPtr->typePtr == Tcl_GetObjType("boolean")) {
    int bool;

    if (Tcl_GetBooleanFromObj(interp, objPtr, &bool) == TCL_OK) {
      return xmlXPathNewBoolean(bool);
    } else {
      return NULL;
    }
  } else if (objPtr->typePtr == Tcl_GetObjType("list")) {
    /*
     * If each of the elements can be converted to a node,
     * then return a nodeset.
     */

    int i, len;
    Tcl_Obj **listPtr;
    xmlNodeSetPtr nset;

    Tcl_ListObjGetElements(interp, objPtr, &len, &listPtr);
    if (len == 0) {
      return xmlXPathNewNodeSet(NULL);
    }

    /*
     * First pass: check that the elements are all nodes.
     */
    for (i = 0; i < len; i++) {
      if (TclXML_libxml2_GetDocFromObj(interp, listPtr[i], &docPtr) == TCL_OK) {
        continue;
      }
      if (TclDOM_libxml2_GetNodeFromObj(interp, listPtr[i], &nodePtr) != TCL_OK) {
        return xmlXPathNewString((const xmlChar *) Tcl_GetStringFromObj(objPtr, NULL));
      }
    }
    /*
     * Now go ahead and create the nodeset (we already did the hard
     * work to create internal reps in pass 1).
     */
    if (TclXML_libxml2_GetDocFromObj(interp, listPtr[0], &docPtr) == TCL_OK) {
      nset = xmlXPathNodeSetCreate((xmlNodePtr) docPtr);
    } else {
      TclDOM_libxml2_GetNodeFromObj(interp, listPtr[0], &nodePtr);
      nset = xmlXPathNodeSetCreate(nodePtr);
    }
    for (i = 1; i < len; i++) {
      if (TclXML_libxml2_GetDocFromObj(interp, listPtr[i], &docPtr) == TCL_OK) {
        xmlXPathNodeSetAdd(nset, (xmlNodePtr) docPtr);
      } else {
        TclDOM_libxml2_GetNodeFromObj(interp, listPtr[i], &nodePtr);
        xmlXPathNodeSetAdd(nset, nodePtr);
      }
    }
    return xmlXPathWrapNodeSet(nset);

  } else {
    return xmlXPathNewString((const xmlChar *) Tcl_GetStringFromObj(objPtr, NULL));
  }
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLT_ConvertXPathObjToTclObj --
 *
 *  Convert an XPath object to a Tcl Object.
 *  Data type is preserved, with nodesets being
 *  mapped to a list of nodes.
 *
 * Results:
 *  Tcl Object.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------------
 */

static Tcl_Obj *
TclXSLT_ConvertXPathObjToTclObj(interp, xpobj)
     Tcl_Interp *interp;
     xmlXPathObjectPtr xpobj;
{
  Tcl_Obj *objPtr;
  int i;

  switch (xpobj->type) {
    case XPATH_XSLT_TREE:
    case XPATH_NODESET:

      objPtr = Tcl_NewListObj(0, NULL);
      if (xpobj->nodesetval) {
	for (i = 0; i < xpobj->nodesetval->nodeNr; i++) {
	  Tcl_Obj *nodeObjPtr = NULL;
	  if (xpobj->nodesetval->nodeTab[i] &&
	      xpobj->nodesetval->nodeTab[i]->type == XML_DOCUMENT_NODE) {
	    nodeObjPtr = TclXML_libxml2_CreateObjFromDoc((xmlDocPtr) xpobj->nodesetval->nodeTab[i]);
	  } else if (xpobj->nodesetval->nodeTab[i]) {
	    nodeObjPtr = TclDOM_libxml2_CreateObjFromNode(interp, xpobj->nodesetval->nodeTab[i]);
	  }
	  Tcl_ListObjAppendElement(interp, objPtr, nodeObjPtr);
	}
      }

      break;

    case XPATH_BOOLEAN:
      objPtr = Tcl_NewBooleanObj(xpobj->boolval);
      break;
      
    case XPATH_NUMBER:
      objPtr = Tcl_NewDoubleObj(xpobj->floatval);
      break;

    case XPATH_STRING:
    case XPATH_UNDEFINED:
    case XPATH_POINT:
    case XPATH_RANGE:
    case XPATH_LOCATIONSET:
    case XPATH_USERS:
    default:
      objPtr = Tcl_NewStringObj((CONST char *) xmlXPathCastToString(xpobj), -1);

      break;
  }

  return objPtr;
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTExtShutdown --
 *
 *  Clean up.
 *
 * Results:
 *  None.
 *
 * Side effects:
 *  None.
 *
 *----------------------------------------------------------------------------
 */

static void
TclXSLTExtShutdown(ctxt, URI, userdata)
     xsltTransformContextPtr ctxt;
     const xmlChar *URI;
     void *userdata;
{
  /* Nothing to do */
}

/*
 *----------------------------------------------------------------------------
 *
 * TclXSLTSecurity --
 * TclXSLTSecurityReadFile --
 * TclXSLTSecurityWriteFile --
 * TclXSLTSecurityCreateDirectory --
 * TclXSLTSecurityReadNetwork --
 * TclXSLTSecurityWriteNetwork --
 *
 *  Check if external operations are permitted.
 *
 * Results:
 *  Returns boolean value.
 *
 * Side effects:
 *  Depends on callback.
 *
 *----------------------------------------------------------------------------
 */

static int
TclXSLTSecurity(name, method, value)
     Tcl_Obj *name;
     const char *method;
     const char *value;
{
  ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  Tcl_Interp *master;
  Tcl_Obj *cmdPtr, *pathPtr;
  int result, permitted;

  if (Tcl_IsSafe(tsdPtr->interp)) {

    /*
     * Invoke hidden command
     */

    master = Tcl_GetMaster(tsdPtr->interp);

    if (!Tcl_IsSafe(master)) {
      return 0;
    }

    if (Tcl_GetInterpPath(master, tsdPtr->interp) != TCL_OK) {
      return 0;
    }
    pathPtr = Tcl_GetObjResult(master);

    cmdPtr = Tcl_NewListObj(0, NULL);
    Tcl_IncrRefCount(cmdPtr);
    Tcl_ListObjAppendElement(master, cmdPtr, Tcl_NewStringObj("interp", -1));
    Tcl_ListObjAppendElement(master, cmdPtr, Tcl_NewStringObj("invokehidden", -1));
    Tcl_ListObjAppendElement(master, cmdPtr, pathPtr);
    Tcl_ListObjAppendElement(master, cmdPtr, Tcl_NewStringObj("-global", -1));
    Tcl_ListObjAppendElement(master, cmdPtr, Tcl_NewStringObj("::xslt::security", -1));
    Tcl_ListObjAppendElement(master, cmdPtr, name);
    Tcl_ListObjAppendElement(master, cmdPtr, Tcl_NewStringObj(method, -1));
    Tcl_ListObjAppendElement(master, cmdPtr, Tcl_NewStringObj(value, -1));

    result = Tcl_EvalObjEx(master, cmdPtr, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);

    Tcl_DecrRefCount(cmdPtr);
  } else {

    /*
     * Invoke command normally
     */

    cmdPtr = Tcl_NewListObj(0, NULL);
    Tcl_IncrRefCount(cmdPtr);
    Tcl_ListObjAppendElement(tsdPtr->interp, cmdPtr, Tcl_NewStringObj("::xslt::security", -1));
    Tcl_ListObjAppendElement(tsdPtr->interp, cmdPtr, name);
    Tcl_ListObjAppendElement(tsdPtr->interp, cmdPtr, Tcl_NewStringObj(method, -1));
    Tcl_ListObjAppendElement(tsdPtr->interp, cmdPtr, Tcl_NewStringObj(value, -1));

    result = Tcl_EvalObjEx(tsdPtr->interp, cmdPtr, TCL_EVAL_GLOBAL|TCL_EVAL_DIRECT);

    Tcl_DecrRefCount(cmdPtr);
  }

  if (result == TCL_OK) {
    if (Tcl_GetBooleanFromObj(tsdPtr->interp, Tcl_GetObjResult(tsdPtr->interp), &permitted) == TCL_OK) {
      return permitted;
    } else if (Tcl_IsSafe(tsdPtr->interp)) {
      return 0;
    } else {
      return 1;
    }
  } else if (Tcl_IsSafe(tsdPtr->interp)) {
    return 0;
  } else {
    return 1;
  }
}
static Tcl_Obj *
TclXSLTSecurityGetName(ctxt)
     xsltTransformContextPtr ctxt;
{
  ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
  Tcl_HashEntry *entryPtr;

  if (ctxt) {
    entryPtr = Tcl_FindHashEntry(tsdPtr->stylesheets, (ClientData) ctxt->style);
    if (entryPtr) {
      return Tcl_NewStringObj((char *) Tcl_GetHashValue(entryPtr), -1);
    } else {
      return Tcl_NewObj();
    }
  } else {
    return Tcl_NewObj();
  }
}
static int
TclXSLTSecurityReadFile(sec, ctxt, value)
     xsltSecurityPrefsPtr sec;
     xsltTransformContextPtr ctxt;
     const char *value;
{
  return TclXSLTSecurity(TclXSLTSecurityGetName(ctxt), "readfile", value);
}
static int
TclXSLTSecurityWriteFile(sec, ctxt, value)
     xsltSecurityPrefsPtr sec;
     xsltTransformContextPtr ctxt;
     const char *value;
{
  return TclXSLTSecurity(TclXSLTSecurityGetName(ctxt), "writefile", value);
}
static int
TclXSLTSecurityCreateDirectory(sec, ctxt, value)
     xsltSecurityPrefsPtr sec;
     xsltTransformContextPtr ctxt;
     const char *value;
{
  return TclXSLTSecurity(TclXSLTSecurityGetName(ctxt), "createdirectory", value);
}
static int
TclXSLTSecurityReadNetwork(sec, ctxt, value)
     xsltSecurityPrefsPtr sec;
     xsltTransformContextPtr ctxt;
     const char *value;
{
  return TclXSLTSecurity(TclXSLTSecurityGetName(ctxt), "readnetwork", value);
}
static int
TclXSLTSecurityWriteNetwork(sec, ctxt, value)
     xsltSecurityPrefsPtr sec;
     xsltTransformContextPtr ctxt;
     const char *value;
{
  return TclXSLTSecurity(TclXSLTSecurityGetName(ctxt), "writenetwork", value);
}
