
/***************************************************************************
                          files.h  -  define file related functions
                             -------------------
    begin                : Sat Nov 10 2001
    copyright            : (C) 2001 by Keith Isdale
    email                : k_isdale@tpg.com.au
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

/* We want skip most of these includes when building documentation */
#ifndef BUILD_DOCS

#include "xsldbg.h"
#include <stdio.h>
#include <libxml/entities.h>
#include <libxml/tree.h>
#include <libxml/catalog.h>
#include <libxml/parserInternals.h>
#include <libxml/encoding.h>    /* needed by filesTranslate, filesEncoding functions */
#include <libxml/uri.h>    /* needed for  xmlURIUnescapeString */
#include "debugXSL.h"
#include "files.h"
#include "utils.h"
#include "options.h"
#include "xsldbgthread.h"
#ifdef WIN32
#include <direct.h>
#endif

#endif /* BUILD_DOCS */


/* top xml document */
static xmlDocPtr topDocument;

/* temporary xml document */
static xmlDocPtr tempDocument;

/* used as a scratch pad for temporary results*/
static xmlChar filesBuffer[DEBUG_BUFFER_SIZE];

/* top stylsheet */
static xsltStylesheetPtr topStylesheet;

/* what is the base path for top stylesheet */
static xmlChar *stylePathName = NULL;

/* what is the path for current working directory*/
static xmlChar *workingDirPath = NULL;

static arrayListPtr entityNameList = NULL;

/* Current encoding to use for standard output*/
static xmlCharEncodingHandlerPtr stdoutEncoding = NULL;

/* input and output buffers for encoding*/
static xmlBufferPtr encodeInBuff = NULL;
static xmlBufferPtr encodeOutBuff = NULL;

/* Current line number and URI for xsldbg*/
static int currentLineNo = -1;
static xmlChar *currentUrl = NULL;

/* -----------------------------------------
   Private function declarations for files.c
 -------------------------------------------*/

/**
 * guessStylesheetHelper:
 * @payload: valid xsltStylesheetPtr
 * @data: valid searchInfoPtr of type SEARCH_NODE
 * @name: not used
 *
 * Try to guess what the complete file/URI is. If successful the search
 *   info will be set to found and the search data will contain the
 *   file name found. We are given our payload via walkStylesheets
 */
static void guessStylesheetHelper(void *payload, void *data,
                                  xmlChar * name);


/**
 * guessStylesheetHelper2:
 * @payload: valid xmlNodePtr of the included stylesheet 
 * @data: valid searchInfoPtr of type SEARCH_NODE
 * @name: not used
 *
 * Try to guess what the complete file/URI is. If successful the search
 *   info will be set to found and the search data will contain the
 *   file name found. We are given our payload via walkIncludes
 */
static void guessStylesheetHelper2(void *payload, void *data,
                                   xmlChar * name);


entityInfoPtr filesNewEntityInfo(const xmlChar * SystemID,
                                 const xmlChar * PublicID);

void filesFreeEntityInfo(entityInfoPtr info);

void filesAddEntityName(const xmlChar * SystemID,
                        const xmlChar * PublicID);


/* ------------------------------------- 
    End private functions
---------------------------------------*/


FILE *terminalIO;

/* No longer needed
   static FILE *oldStdin, *oldStdout, *oldStderr;*/


//static char *ttyName = NULL;           /* what is the name of the default terminal */
static char *termName = NULL;   /* what is the name of terminal we are redirected to */


/**
 * redirectToTerminal:
 * @device: terminal to redirect i/o to , will not work under win32
 *
 * Open communications to the terminal device @device
 *
 * Returns 1 if sucessful
 *         0 otherwise
 */
int
openTerminal(xmlChar * device)
{
    int result = 0;

    if (!device) {              /* Failed; there's no device */
#ifdef WITH_XSLDBG_DEBUG_PROCESS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: NULL argument provided\n");
#endif
        return result;
    }

    /*
     * On RISC OS, you get one terminal - the screen.
     * we assume that the parameter is meant to be an output device as
     * per normal - we can use vdu:, rawvdu: or :tt, or a filename for
     * normal VDU output, VDU output without newline expansion,
     * C terminal output with control code escaping, or a raw file
     * respectively.
     * The name passed is expected to be in native file format - no
     * URI escaping here.
     * One assumes that you might use a socket or a pipe here.
     */

    if (terminalIO) {
        fclose(terminalIO);
        terminalIO = NULL;
    }


    switch (device[0]) {
        case '\0':
        case '0':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
            /* look like we are supposed to close the terminal 
             * but we've already done that     
             */
            break;

        case '1':
            if (termName) {
                terminalIO = fopen((char *) termName, "w");
                if (terminalIO != NULL) {
                    xmlFree(termName);
                    termName = xmlMemStrdup((char *) device);
                    result = 1;
                } else {
                    xsldbgGenericErrorFunc(i18n("Error: Unable to open terminal %1.\n").arg(xsldbgText(termName)));
                }
            } else {
                xsldbgGenericErrorFunc(i18n("Error: Did not previously open terminal.\n"));
            }
            break;

        case '2':
#ifdef WITH_XSLDBG_DEBUG_PROCESS
            xsltGenericError(xsltGenericErrorContext,
                             "Warning: Terminal level 2 not implemented\n");
#endif
            break;


        default:
            terminalIO = fopen((char *) device, "w");
            if (terminalIO != NULL) {
                if (termName)
                    xmlFree(termName);
                termName = xmlMemStrdup((char *) device);
                result = 1;
            } else {
                xsldbgGenericErrorFunc(i18n("Error: Unable to open terminal %1.\n").arg(xsldbgText(device)));
            }

    }

    return result;
}


/**
 * guessStylesheetHelper:
 * @payload: valid xsltStylesheetPtr
 * @data: valid searchInfoPtr of type SEARCH_NODE
 * @name: not used
 *
 * Try to guess what the complete file/URI is. If successful the search
 *   info will be set to found and the search data will contain the
 *   file name found. We are given our payload via walkStylesheets
 */
void
guessStylesheetHelper(void *payload, void *data,
                      xmlChar * name)
{
    Q_UNUSED(name);
    xsltStylesheetPtr style = (xsltStylesheetPtr) payload;
    searchInfoPtr searchCriteria = (searchInfoPtr) data;
    nodeSearchDataPtr searchData = NULL;
    /* where did the directory/URI separator occur */
    char *lastSlash;

    if (!style || !style->doc || !searchCriteria || !searchCriteria->data
        || (searchCriteria->type != SEARCH_NODE))
        return;

    searchData = (nodeSearchDataPtr) searchCriteria->data;
    if (searchData->nameInput && (searchData->absoluteNameMatch == NULL)) {
        /* at this point we know that we have not made an absolute match 
         * but we may have made a relative match */
        if (xmlStrCmp(style->doc->URL, searchData->nameInput) == 0) {
            /* absolute path match great! */
            searchData->absoluteNameMatch =
                (xmlChar *) xmlMemStrdup((char *) style->doc->URL);
            searchData->node = (xmlNodePtr) style->doc;
            searchCriteria->found = 1;
            return;
        }


        /* try to guess we assume that the files are unique */
        xmlStrCpy(filesBuffer, "__#!__");
        /* try relative to top stylesheet directory */
        if (stylePath()) {
            xmlStrCpy(filesBuffer, stylePath());
            xmlStrCat(filesBuffer, searchData->nameInput);
        }
        if (xmlStrCmp(style->doc->URL, filesBuffer) == 0) {
            /* guessed right! */
            searchData->guessedNameMatch =
                (xmlChar *) xmlMemStrdup((char *) filesBuffer);
            searchData->node = (xmlNodePtr) style->doc;
            searchCriteria->found = 1;
            return;
        }

        if (workingPath()) {
            /* try relative to working directory */
            xmlStrCpy(filesBuffer, workingPath());
            xmlStrCat(filesBuffer, searchData->nameInput);
        }
        if (xmlStrCmp(style->doc->URL, filesBuffer) == 0) {
            /* guessed right! */
            searchData->guessedNameMatch =
                (xmlChar *) xmlMemStrdup((char *) filesBuffer);
            searchData->node = (xmlNodePtr) style->doc;
            searchCriteria->found = 1;
            return;
        }


	/* Find the last separator of the stylsheet's URL */
	lastSlash = xmlStrChr(style->doc->URL, URISEPARATORCHAR);
	if (!lastSlash)
	  lastSlash = xmlStrChr(style->doc->URL, PATHCHAR);

        if (lastSlash) {
            /* Last try, assume nameInput contains only a file name
             * Strip of the file name at end of the stylesheet doc URL */
	  lastSlash++;    /* skip the slash */
	  if (xmlStrCmp(lastSlash, searchData->nameInput) == 0) {
	    /* guessed right! */
	    searchData->guessedNameMatch =
                        (xmlChar *) xmlMemStrdup((char *) style->doc->URL);
                    searchData->node = (xmlNodePtr) style->doc;
                    searchCriteria->found = 1;
                }
            }
    }
}


/**
 * guessStylesheetHelper2:
 * @payload: valid xmlNodePtr of the included stylesheet 
 * @data: valid searchInfoPtr of type SEARCH_NODE
 * @name: not used
 *
 * Try to guess what the complete file/URI is. If successful the search
 *   info will be set to found and the search data will contain the
 *   file name found. We are given our payload via walkIncludes
 */
void
guessStylesheetHelper2(void *payload, void *data,
                       xmlChar * name)
{
    Q_UNUSED(name);
    xmlNodePtr node = (xmlNodePtr) payload;
    searchInfoPtr searchCriteria = (searchInfoPtr) data;
    nodeSearchDataPtr searchData = NULL;
    /* where did the directory/URI separator occur */
    char *lastSlash;

    if (!node || !node->doc || !searchCriteria || !searchCriteria->data ||
        (searchCriteria->type != SEARCH_NODE))
        return;

    searchData = (nodeSearchDataPtr) searchCriteria->data;
    if (searchData->nameInput && (searchData->absoluteNameMatch == NULL)) {
        /* at this point we know that we have not made an absolute match 
         * but we may have made a relative match */
        if (xmlStrCmp(node->doc->URL, searchData->nameInput) == 0) {
            /* absolute path match great! */
            searchData->absoluteNameMatch =
                (xmlChar *) xmlMemStrdup((char *) node->doc->URL);
            searchData->node = node;
            searchCriteria->found = 1;
            return;
        }


        /* try to guess we assume that the files are unique */
        xmlStrCpy(filesBuffer, "__#!__");
        /* try relative to top stylesheet directory */
        if (stylePath()) {
            xmlStrCpy(filesBuffer, stylePath());
            xmlStrCat(filesBuffer, searchData->nameInput);
        }
        if (xmlStrCmp(node->doc->URL, filesBuffer) == 0) {
            /* guessed right! */
            searchData->guessedNameMatch =
                (xmlChar *) xmlMemStrdup((char *) filesBuffer);
            searchData->node = node;
            searchCriteria->found = 1;
            return;
        }

        if (workingPath()) {
            /* try relative to working directory */
            xmlStrCpy(filesBuffer, workingPath());
            xmlStrCat(filesBuffer, searchData->nameInput);
        }
        if (xmlStrCmp(node->doc->URL, filesBuffer) == 0) {
            /* guessed right! */
            searchData->guessedNameMatch =
                (xmlChar *) xmlMemStrdup((char *) filesBuffer);
            searchData->node = node;
            searchCriteria->found = 1;
            return;
        }


	/* Find the last separator of the stylsheet's URL */
	lastSlash = xmlStrChr(node->doc->URL, URISEPARATORCHAR);
	if (!lastSlash)
	  lastSlash = xmlStrChr(node->doc->URL, PATHCHAR);

        if (lastSlash) {
	  /* Last try, assume nameInput contains only a file name
	   * Strip of the file name at end of the stylesheet doc URL */
	  lastSlash++;    /* skip the slash */
	  if (xmlStrCmp(lastSlash, searchData->nameInput) == 0) {
	    /* guessed right! */
	    searchData->guessedNameMatch =
                        (xmlChar *) xmlMemStrdup((char *) node->doc->URL);
	    searchData->node = node;
	    searchCriteria->found = 1;
	  }
	}
    }
}


/**
 * guessStylesheetName:
 * @searchInf: Is valid
 *
 * Try to find a matching stylesheet name
 * Sets the values in @searchinf depending on outcome of search
 */
void
guessStylesheetName(searchInfoPtr searchCriteria)
{
    nodeSearchDataPtr searchData;

    if (!searchCriteria)
        return;

    searchData = (nodeSearchDataPtr) searchCriteria->data;
    if (searchData->nameInput == NULL)
        return;                 /* must supply name of file to look for */

    walkStylesheets((xmlHashScanner) guessStylesheetHelper,
                    searchCriteria, filesGetStylesheet());
    if (!searchCriteria->found) {
        /* try looking in the included stylesheets */
        walkIncludes((xmlHashScanner) guessStylesheetHelper2,
                     searchCriteria, filesGetStylesheet());
    }
}


/**
 * stylePath:
 *
 * Return The base path for the top stylesheet ie
 *        ie URL minus the actual file name
 *
 * Returns The base path for the top stylesheet ie
 *        ie URL minus the actual file name
 */
xmlChar *
stylePath(void)
{
    return stylePathName;
}


/**
 * workingPath:
 * 
 * Return the working directory as set by changeDir function
 *
 * Returns The working directory as set by changeDir function
 */
xmlChar *
workingPath(void)
{
    return workingDirPath;
}


/**
 * changeDir:
 * @path: The path to adopt as new working directory
 *
 * Change working directory to path 
 *
 * Returns 1 on success,
 *         0 otherwise
 */
int
changeDir(const xmlChar * path)
{
    int result = 0;
    int charIndex;
    const char endString[2] = { PATHCHAR, '\0' };
    xmlChar *expandedName = NULL;


    if (path && (xmlStrLen(path) > 0)) {
        expandedName = filesExpandName(path);
    } else {
#ifdef WITH_XSLDBG_DEBUG_PROCESS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Empty path provided to changeDir");
#endif
        return result;
    }

    if (!expandedName)
        return result;          /* out of memory ? */

    if (xmlStrLen(expandedName) + 1 > sizeof(filesBuffer)) {
        xsldbgGenericErrorFunc(i18n("Error: The file name \"%1\" is too long.\n").arg(xsldbgText(path)));
        return result;
    }

    xmlStrCpy(filesBuffer, expandedName);
    /* strip off any extra PATHCHAR's as win32's chdir function 
     * fails if we don't */
    charIndex = xmlStrLen(filesBuffer) - 1;
    while (charIndex && (filesBuffer[charIndex] == PATHCHAR)) {
        charIndex--;
    }
    filesBuffer[charIndex + 1] = '\0';


    if (chdir((char *) filesBuffer) == 0) {
        if (workingDirPath)
            xmlFree(workingDirPath);
        /* must have path char at end of path name */
        xmlStrCat(filesBuffer, endString);
        workingDirPath = (xmlChar *) xmlMemStrdup((char *) filesBuffer);
        result = 1;
    }
    xmlFree(expandedName);      /* this will always be valid time */
    if (!result) {
        xsldbgGenericErrorFunc(i18n("Error: Unable to change to directory %1.\n").arg(xsldbgText(path)));
    } else {
	if (xslDebugStatus != DEBUG_NONE)
	    xsldbgGenericErrorFunc(i18n("Changed to directory %1.\n").arg(xsldbgText(path)));
    }
    return result;
}


/**
 * filesLoadXmlFile:
 * @path: xml file to load
 * @fileType: A valid FileTypeEnum 
 * 
 * Load specified file type, freeing any memory previously used 
 *
 * Returns 1 on success,
 *         0 otherwise 
 */
int
filesLoadXmlFile(const xmlChar * path, FileTypeEnum fileType)
{
    int result = 0;

    if (!filesFreeXmlFile(fileType))
        return result;

    switch (fileType) {
        case FILES_XMLFILE_TYPE:
            if (path && xmlStrLen(path)) {
                if (optionsGetIntOption(OPTIONS_SHELL)) {
                    xsldbgGenericErrorFunc(i18n("Setting XML Data file name to %1.\n").arg(xsldbgText(path)));
                }
                optionsSetStringOption(OPTIONS_DATA_FILE_NAME, path);
            }
            topDocument = xsldbgLoadXmlData();
            if (topDocument)
                result = 1;
            break;

        case FILES_SOURCEFILE_TYPE:
            if (path && xmlStrLen(path)) {
                if (optionsGetIntOption(OPTIONS_SHELL)) {
                    xsldbgGenericErrorFunc(i18n("Setting stylesheet file name to %1.\n").arg(xsldbgText(path)));
                }
                optionsSetStringOption(OPTIONS_SOURCE_FILE_NAME, path);
            }
            topStylesheet = xsldbgLoadStylesheet();
            if (topStylesheet && topStylesheet->doc) {
                /* look for last slash (or baskslash) of URL */
                char *lastSlash = xmlStrrChr(topStylesheet->doc->URL,
                                             PATHCHAR);
                const char *docUrl =
                    (const char *) topStylesheet->doc->URL;

                result = 1;
                if (docUrl && lastSlash) {
                    stylePathName = (xmlChar *) xmlMemStrdup(docUrl);
                    stylePathName[lastSlash - docUrl + 1] = '\0';
                    if (optionsGetIntOption(OPTIONS_SHELL)) {
                        xsldbgGenericErrorFunc(i18n("Setting stylesheet base path to %1.\n").arg(xsldbgText(stylePathName)));
                    }
                } else {
                    const char cwd[4] = { '.', PATHCHAR, '\0' };

                    /* ie for *nix this becomes "./" */
                    stylePathName = xmlStrdup(BAD_CAST cwd);
                }

                /* try to find encoding for this stylesheet */
                if (optionsGetIntOption(OPTIONS_AUTOENCODE))
                    filesSetEncoding((char *) topStylesheet->encoding);
            }
            break;

        case FILES_TEMPORARYFILE_TYPE:
            if (!path || !xmlStrLen(path)) {
                xsldbgGenericErrorFunc(i18n("Missing file name.\n"));
                break;
            }
            topDocument = xsldbgLoadXmlTemporary(path);
            if (tempDocument)
                result = 1;
            break;
    }
    return result;
}


/**
 * filesFreeXmlFile:
 * @fileType: A valid FileTypeEnum 
 * 
 * Free memory associated with the xml file 
 *
 * Returns 1 on success,
 *         0 otherwise
 */
int
filesFreeXmlFile(FileTypeEnum fileType)
{
    int result = 0, type = fileType;

    switch (type) {
        case FILES_XMLFILE_TYPE:
            if (topDocument)
                xmlFreeDoc(topDocument);
            topDocument = NULL;
            result = 1;
            break;

        case FILES_SOURCEFILE_TYPE:
            if (topStylesheet)
                xsltFreeStylesheet(topStylesheet);
            if (stylePathName)
                xmlFree(stylePathName);
            stylePathName = NULL;
            topStylesheet = NULL;
            result = 1;
            break;

        case FILES_TEMPORARYFILE_TYPE:
            if (tempDocument)
                xmlFreeDoc(tempDocument);
            tempDocument = NULL;
            result = 1;
            break;
    }
    return result;
}


/**
 * filesGetStylesheet:
 *
 * Return The topmost stylesheet non-null on success,
 *         NULL otherwise
 *
 * Returns The topmost stylesheet non-null on success,
 *         NULL otherwise
 */
xsltStylesheetPtr
filesGetStylesheet(void)
{
    return topStylesheet;
}


/**
 * filesGetTemporaryDoc:
 *
 * Return The current "temporary" document
 *
 * Returns The current "temporary" document
 */
xmlDocPtr
filesGetTemporaryDoc(void)
{
    return tempDocument;
}


/**
 * filesGetMainDoc:
 *
 * Return The main docment
 *
 * Returns The main docment
 */
xmlDocPtr
filesGetMainDoc(void)
{
    return topDocument;
}


/**
 * filesReloaded:
 * @reloaded: if = -1 then ignore @reloaded
 *             otherwise change the status of files to value of @reloaded   
 *
 * Returns 1 if stylesheet or its xml data file has been "flaged" as reloaded,
 *         0 otherwise
 */
int
filesReloaded(int reloaded)
{
    static int changed = 0;

    if (reloaded >= 0) {
        changed = reloaded;
    }

    return changed;
}



/**
 * filesInit:
 *
 * Initialize the file related structures
 * Returns 1 on success,
 *         0 otherwise
 */
int
filesInit(void)
{
    int result = 0;

    terminalIO = NULL;
#ifdef __riscos
    ttyName = ":tt";            /* Default tty */
#endif
#ifdef HAVE_UNISTD
    ttyName = ttyname(fileno(stdin));
    /* save out io for when/if we send debugging to a terminal */
    oldStdin = stdin;
    oldStdout = stdout;
    oldStderr = stderr;
#endif
    topDocument = NULL;
    tempDocument = NULL;
    topStylesheet = NULL;
    entityNameList = arrayListNew(4, (freeItemFunc) filesFreeEntityInfo);
#if defined(HAVE_INCLUDE_FIX) && (LIBXML_VERSION < 20508)
    xmlSetEntityReferenceFunc(filesEntityRef);
#endif

    /* setup the encoding */
    encodeInBuff = xmlBufferCreate();
    encodeOutBuff = xmlBufferCreate();

    /* check the result so far and lastly perform platform specific
     * initialization */
    if (entityNameList && encodeInBuff && encodeOutBuff &&
        filesPlatformInit())
        result = 1;
    return result;
}

/**
 * filesFree:
 *
 * Free memory used by file related structures
 */
void
filesFree(void)
{
    int result;

    if (terminalIO) {
        fclose(terminalIO);
        terminalIO = NULL;
    }
    if (termName) {
        xmlFree(termName);
        termName = NULL;
    }

    result = filesFreeXmlFile(FILES_SOURCEFILE_TYPE);
    if (result)
        result = filesFreeXmlFile(FILES_XMLFILE_TYPE);
    if (result)
        result = filesFreeXmlFile(FILES_TEMPORARYFILE_TYPE);
    if (!result){
#ifdef WITH_XSLDBG_DEBUG_PROCESS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: Unable to free memory used by XML/XSLT files\n");
#endif
    }
    if (stylePathName) {
        xmlFree(stylePathName);
        stylePathName = NULL;
    }

    if (workingDirPath) {
        xmlFree(workingDirPath);
        workingDirPath = NULL;
    }

    if (entityNameList) {
        arrayListFree(entityNameList);
        entityNameList = NULL;
    }

    /* Free memory used by encoding related structures */
    if (encodeInBuff)
        xmlBufferFree(encodeInBuff);

    if (encodeOutBuff)
        xmlBufferFree(encodeOutBuff);

    /* close current encoding */
    filesSetEncoding(NULL);

    if (currentUrl)
        xmlFree(currentUrl);

    /* free any memory used by platform specific files module */
    filesPlatformFree();
}


/**
 * filesIsSourceFile:
 * @fileName : is valid
 * 
 * Returns true if @name has the ".xsl" externsion
 */
int
filesIsSourceFile(xmlChar * fileName)
{
    return strstr((char *) fileName, ".xsl") ||
        strstr((char *) fileName, ".Xsl") ||
        strstr((char *) fileName, ".XSL");
}



entityInfoPtr
filesNewEntityInfo(const xmlChar * SystemID, const xmlChar * PublicID)
{

    entityInfoPtr result = (entityInfoPtr) xmlMalloc(sizeof(entityInfo));

    if (result) {
        if (SystemID)
            result->SystemID = xmlStrdup(SystemID);
        else
            result->SystemID = xmlStrdup(BAD_CAST "");

        if (PublicID)
            result->PublicID = xmlStrdup(PublicID);
        else
            result->PublicID = xmlStrdup(BAD_CAST "");
    }
    return result;
}

void
filesFreeEntityInfo(entityInfoPtr info)
{
    if (!info)
        return;

    if (info->SystemID)
        xmlFree(info->SystemID);

    if (info->PublicID)
        xmlFree(info->PublicID);
    xmlFree(info);
}

/**
 * filesAddEntityName:
 * @name : is valid
 *
 * Add name to entity name list of know external entities if 
 *  it does not already exist in list
 */
void

filesAddEntityName(const xmlChar * SystemID, const xmlChar * PublicID)
{
    int entityIndex = 0;
    entityInfoPtr tempItem;

    if (!SystemID || !filesEntityList())
        return;

    for (entityIndex = 0;
         entityIndex < arrayListCount(filesEntityList()); entityIndex++) {
        tempItem =
            (entityInfoPtr) arrayListGet(filesEntityList(), entityIndex);
        if (tempItem && xmlStrEqual(SystemID, tempItem->SystemID)) {
            /* name aready exits so don't add it */
            return;
        }

    }

    tempItem = filesNewEntityInfo(SystemID, PublicID);
    arrayListAdd(filesEntityList(), tempItem);
}


/**
 * filesEntityRef :
 * @ent : Is valid as provided by libxslt
 * @firstNode : Is valid
 * @lastNode : Is Valid
 *
 * Fixes the nodes from firstNode to lastNode so that debugging can occur
   */
void

filesEntityRef(xmlEntityPtr ent, xmlNodePtr firstNode, xmlNodePtr lastNode)
{
    xmlNodePtr node = firstNode;
    if (!firstNode || !ent || !ent->SystemID ||
         (ent->etype != XML_EXTERNAL_GENERAL_PARSED_ENTITY) )
        return;

    if (ent->ExternalID)
        filesAddEntityName(ent->SystemID, ent->ExternalID);
    else
        filesAddEntityName(ent->URI, BAD_CAST "");
    while (node){
        filesSetBaseUri(node, ent->URI);
	if (node != lastNode)
           node = node->next;
	else
	   node = NULL;
    }
}



 /**
   * filesSetBaseUri:
   * @node : Is valid and has a doc parent
   * @uri : Is Valid
   * 
   * Set the base uri for this node. Function is used when xml file
   *    has external entities in its DTD
   * 
   * Returns 1 if successful,
   *        0 otherwise
   */

int
filesSetBaseUri(xmlNodePtr node, const xmlChar * uri)
{
    int result = 0;

    if (!node || !uri)
        return result;
    else {
        if (node->type == XML_ELEMENT_NODE){
            xmlChar *xsldbgUrlCopy = xmlGetProp(node, BAD_CAST "xsldbg:uri");
            if (!xsldbgUrlCopy)
                xmlNewProp(node, BAD_CAST "xsldbg:uri", uri);
            else
                xmlFree(xsldbgUrlCopy);
        }
        result = 1;
    }
    return result;
}


  /**
   * filesGetBaseUri:
   * @node : Is valid and has a doc parent
   * 
   * Get a copy of the base uri for this node. Function is most usefull 
   *  used when xml file  has external entities in its DTD
   * 
   * Returns the a copy of the base uri for this node,
   *         NULL otherwise
   */
xmlChar *
filesGetBaseUri(xmlNodePtr node)
{
    xmlChar *result = NULL;

    if (!node || !node->doc)
        return result;

    while (node && node->parent) {
        /*
         * result =  xmlGetNsProp(node, BAD_CAST "uri", XSLDBG_XML_NAMESPACE);
         */
        if (node->type == XML_ELEMENT_NODE) {
            result = xmlGetProp(node, BAD_CAST "xsldbg:uri");
            if (result)
                break;
        }
        node = node->parent;
    }

    if (!result && node->doc && node->doc->URL)
        result = xmlStrdup(node->doc->URL);

    return result;
}




/**
 * filesEntityList:
 *
 * Return the list entity names used for documents loaded
 *
 * Returns the list entity names used for documents loaded
 */
arrayListPtr
filesEntityList(void)
{
    return entityNameList;
}

extern int intVolitileOptions[OPTIONS_LAST_INT_OPTIONID - OPTIONS_FIRST_INT_OPTIONID + 1];

/**
 * filesLoadCatalogs:
 *
 * Load the catalogs specifed by OPTIONS_CATALOG_NAMES if 
 *      OPTIONS_CATALOGS is enabled
 * Returns 1 if sucessful
 *         0 otherwise   
 */
int
filesLoadCatalogs(void)
{
    int result = 0;
    const char *catalogs = NULL;

    xmlCatalogCleanup();
    if (optionsGetIntOption(OPTIONS_CATALOGS)) {
      if (optionsGetStringOption(OPTIONS_CATALOG_NAMES) == NULL) {
	/* use the SGML catalog */
#ifdef __riscos
	catalogs = getenv("SGML$CatalogFiles");
#else
	catalogs = getenv("SGML_CATALOG_FILES");
#endif
	if (catalogs == NULL) {
#ifdef __riscos
	  xsldbgGenericErrorFunc("Warning: Environment variable SGML$CatalogFiles is not set.\n");
#else
	  xsldbgGenericErrorFunc("Warning: Environment variabe SGML_CATALOG_FILES FILES not set.\n");
#endif
	} else
	  /* copy the current catalog name(s) for user to see */
	  optionsSetStringOption(OPTIONS_CATALOG_NAMES,
				 (xmlChar *) catalogs);
      } else
	/* Use the current catalog settings from users*/
	catalogs = (char *)
	  optionsGetStringOption(OPTIONS_CATALOG_NAMES);

      result = 1;
    }

    if (catalogs){
      /* Load the new cataog selection */
      xmlLoadCatalogs(catalogs);
    }else{
      /* Use default catalogs */
      xmlInitializeCatalog();
    }
    return result;
}




  /**
   * filesEncode:
   * @text: Is valid, text to translate from UTF-8, 
   *
   * Return  A  new string of converted @text
   *
   * Returns  A  new string of converted @text, may be NULL
   */
xmlChar *
filesEncode(const xmlChar * text)
{
    xmlChar *result = NULL;

    if (!text)
        return result;

    if (!stdoutEncoding || !encodeInBuff || !encodeOutBuff)
        return xmlStrdup(text); /* no encoding active return as UTF-8 */

    xmlBufferEmpty(encodeInBuff);
    xmlBufferEmpty(encodeOutBuff);
    xmlBufferCat(encodeInBuff, text);

    if (xmlCharEncOutFunc(stdoutEncoding, encodeOutBuff, encodeInBuff)
        >= 0) {
        result = xmlStrdup(xmlBufferContent(encodeOutBuff));
    } else {
        xsldbgGenericErrorFunc(i18n("Encoding of text failed.\n"));
        return xmlStrdup(text); /*  panic,  return as UTF-8 */
    }
    return result;
}



  /**
   * filesDeccode:
   * @text: Is valid, text to translate from current encoding to UTF-8, 
   *
   * Return  A  string of converted @text
   *
   * Returns  A  string of converted @text, may be NULL
   */
xmlChar *
filesDecode(const xmlChar * text)
{
    xmlChar *result = NULL;

    if (!text)
        return result;

    if (!stdoutEncoding || !encodeInBuff || !encodeOutBuff)
        return xmlStrdup(text); /* no encoding active return as UTF-8 */

    xmlBufferEmpty(encodeInBuff);
    xmlBufferEmpty(encodeOutBuff);
    xmlBufferCat(encodeInBuff, text);

    if (xmlCharEncInFunc(stdoutEncoding, encodeOutBuff, encodeInBuff)
        >= 0) {
        result = xmlStrdup(xmlBufferContent(encodeOutBuff));
    } else {
        xsldbgGenericErrorFunc(i18n("Encoding of text failed.\n"));
        return xmlStrdup(text); /*  panic,  return @text unchanged */
    }
    return result;
}


    /*
     * filesSetEncoding:
     * @encoding : Is a valid encoding supported by the iconv library or NULL
     *
     * Opens encoding for all standard output to @encoding. If  @encoding 
     *        is NULL then close current encoding and use UTF-8 as output encoding
     *
     * Returns 1 if successful in setting the encoding of all standard output
     *           to @encoding
     *         0 otherwise
     */
int
filesSetEncoding(const char *encoding)
{
    int result = 0;

    if (encoding) {
        /* don't switch encoding unless we've found a valid encoding */
        xmlCharEncodingHandlerPtr tempEncoding =
            xmlFindCharEncodingHandler(encoding);
        if (tempEncoding) {
            filesSetEncoding(NULL);     /* re-use code to close encoding */
            stdoutEncoding = tempEncoding;
            result =
                (xmlCharEncOutFunc(stdoutEncoding, encodeOutBuff, NULL)
                 >= 0);
            if (!result) {
                xmlCharEncCloseFunc(stdoutEncoding);
                stdoutEncoding = NULL;
                xsldbgGenericErrorFunc(i18n("Unable to initialize encoding %1.").arg(xsldbgText(encoding)));
            } else
                optionsSetStringOption(OPTIONS_ENCODING,
                                       (xmlChar *) encoding);
        } else {
            xsldbgGenericErrorFunc(i18n("Invalid encoding %1.\n").arg(xsldbgText(encoding)));
        }
    } else {
        /* close encoding and use UTF-8 */
        if (stdoutEncoding)
            result = (xmlCharEncCloseFunc(stdoutEncoding) >= 0);
        else
            result = 1;
        stdoutEncoding = NULL;
    }
    return result;
}



  /**
   * filesMoreFile:
   * @fileName : May be NULL
   * @file : May be NULL
   *
   * Do a "more" like print of file specified by @fileName OR
   *   @file. If both are provided @file will be used. The content 
   *   of file chosen must be in UTF-8, and will be  printed in 
   *   the current encoding selected.The function will pause output 
   *   after FILES_NO_LINES lines have been printed waiting for
   *   user to enter "q" to quit or any other text to continue.
   *
   * Returns 1 if successful,
   *         0 otherwise
   */
int
filesMoreFile(const xmlChar * fileName, FILE * file)
{
    int result = 0;
    int openedFile = 0;
    int lineCount;
    int reachedEof = 0;

    if (fileName && !file) {
#ifdef __riscos
        /* convert into RISC OS format a *nix style file name */
        fileName = (const xmlChar *) riscosfilename((char *) fileName);
#endif
        file = fopen((char *) fileName, "r");
        openedFile = 1;         /* since we opened the file we must close it */
    }
    if (file) {
        while (!feof(file) && !reachedEof) {
            lineCount = 0;
            while (!feof(file) && (lineCount < FILES_NO_LINES) &&
                   !reachedEof) {
                if (fgets((char *) filesBuffer, sizeof(filesBuffer), file)) {
                    xsltGenericError(xsltGenericErrorContext, "%s",
                                     filesBuffer);
                    lineCount++;
                } else {
                    reachedEof = 1;
                }
            }

            if (!feof(file) && !reachedEof) {
                xsldbgGenericErrorFunc(i18n(" ----- more ---- \n"));
                fflush(stderr);
                if (fgets((char *) filesBuffer, sizeof(filesBuffer), stdin)) {
                    if ((*filesBuffer == 'q') || (*filesBuffer == 'Q'))
                        reachedEof = 1;
                } else {
                    reachedEof = 1;
                }
            }
        }

        if (openedFile) {
            fclose(file);
        }
        xsltGenericError(xsltGenericErrorContext, "\n");
        result = 1;
    } else {
#ifdef WITH_XSLDBG_DEBUG_PROCESS
        xsltGenericError(xsltGenericErrorContext,
                         "Error: No valid file provided to print\n");
#endif
    }


    return result;
}


  /**
   * filesSearchResultsPath:
   *
   * Get the base path to be used for storing search results
   *
   * Returns The base path to be used for storing search results
   */
const xmlChar *
filesSearchResultsPath()
{
    const xmlChar *result;

    if (optionsGetStringOption(OPTIONS_SEARCH_RESULTS_PATH))
        result = optionsGetStringOption(OPTIONS_SEARCH_RESULTS_PATH);
    else
        result = stylePath();

    return result;
}


  /**
   * filesURItoFileName:
   * @uri : A valid URI that uses the "file://" prefix
   *
   * Return A copy of the conversion of @uri to a file name
   *        that is suitable to be used  with the fopen function.
   *        May be NULL, if out of memory, @uri does not use the
   *        "file://" prefix, or unable to convert to a valid file name
   *
   * Returns A copy of the conversion of @uri to a file name
   *        that is suitable to be used with the fopen function.
   *        May be NULL, if out of memory, @uri does not use the
   *        "file://" prefix, or unable to convert to a valid file name
   *    
   */
xmlChar *filesURItoFileName(const xmlChar* uri)
{
  xmlChar *result = NULL;
  xmlChar *unescapedFileName = NULL;
  const xmlChar* tempName = NULL;

  if (uri){
    if (!xmlStrnCmp(uri, "file://localhost", 16 )){
      tempName = uri + 16;
    }else{
#if defined(WIN32) && ! defined(CYGWIN)
      if (!xmlStrnCmp(uri, "file:///", 8))
	tempName = uri + 8;
#else
      if (!xmlStrnCmp(uri, "file://", 7))
	tempName = uri + 6; /* we need the leading '/'*/
#endif
    }

    /* If we've found something check to see if the file name 
       found is to be valid */
    if (tempName)
      result = (xmlChar*) xmlStrdup(tempName);
      unescapedFileName =  (xmlChar*) xmlStrdup(tempName);
      if (result && unescapedFileName){
	if (PATHCHAR != URISEPARATORCHAR){
	  /* Must convert path separators first */
	  xmlChar *probe = result;
	  while(*probe != '\0'){
	    if (*probe == (xmlChar)URISEPARATORCHAR)
	      *probe = (xmlChar)PATHCHAR;
	    probe++;
	  }
	}
	/* Now unescape the file name in result so far
	* NB: An unescaped name takes less memory that an escaped name
	*/
	xmlURIUnescapeString((char*)result, -1,  (char*)unescapedFileName);
	xmlFree(result);
	/* success we've got an local unescaped file name */
	result = unescapedFileName;
      }else{
        xsldbgGenericErrorFunc(i18n("Error: Out of memory.\n"));	
	if (result){
	  xmlFree(result);
	}
	if (unescapedFileName) /* not needed, here for completeness */
	  xmlFree(unescapedFileName);

	result = NULL;
      }
    }else{
        xsldbgGenericErrorFunc(i18n("Error: Unable to convert %1 to local file name.\n").arg(xsldbgText(uri)));
    }


  return result;
}


/* TODO in xsldbg 3.x rename these to use files prefix */

/**
 * xsldbgUpdateFileDetails:
 * @node : A valid node
 * 
 * Update the URL and  line number that we stoped at 
 */
void
xsldbgUpdateFileDetails(xmlNodePtr node)
{
    if ((node != NULL) && (node->doc != NULL)
        && (node->doc->URL != NULL)) {
        if (currentUrl != NULL)
            xmlFree(currentUrl);
        currentUrl =  filesGetBaseUri(node);
        currentLineNo = xmlGetLineNo(node);
    }
}


/**
 * xsldbgLineNo:
 *
 * What line number are we at 
 *
 * Returns The current line number of xsldbg, may be -1
 **/
int
xsldbgLineNo(void)
{
    return currentLineNo;
}


/**
 * xsldbgUrl:
 * 
 * What URL did we stop at
 *
 * Returns A NEW copy of URL stopped at. Caller must free memory for URL.
 *  May be NULL  
 */
xmlChar *
xsldbgUrl(void)
{
    if (currentUrl != NULL)
        return (xmlChar *) xmlMemStrdup((char *) currentUrl);
    else
        return NULL;
}
