/*
 * runtest.c: C program to run libxml2 regression tests without
 *            requiring make or Python, and reducing platform dependencies
 *            to a strict minimum.
 *
 * To compile on Unixes:
 * cc -o runtest `xml2-config --cflags` runtest.c `xml2-config --libs` -lpthread
 *
 * See Copyright for the status of this software.
 *
 * daniel@veillard.com
 */

#include "libxml.h"
#include <stdio.h>

#if !defined(_WIN32) || defined(__CYGWIN__)
#include <unistd.h>
#endif
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/tree.h>
#include <libxml/uri.h>
#include <libxml/encoding.h>

#ifdef LIBXML_OUTPUT_ENABLED
#ifdef LIBXML_READER_ENABLED
#include <libxml/xmlreader.h>
#endif

#ifdef LIBXML_XINCLUDE_ENABLED
#include <libxml/xinclude.h>
#endif

#ifdef LIBXML_XPATH_ENABLED
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#ifdef LIBXML_XPTR_ENABLED
#include <libxml/xpointer.h>
#endif
#endif

#ifdef LIBXML_SCHEMAS_ENABLED
#include <libxml/relaxng.h>
#include <libxml/xmlschemas.h>
#include <libxml/xmlschemastypes.h>
#endif

#ifdef LIBXML_PATTERN_ENABLED
#include <libxml/pattern.h>
#endif

#ifdef LIBXML_C14N_ENABLED
#include <libxml/c14n.h>
#endif

#ifdef LIBXML_HTML_ENABLED
#include <libxml/HTMLparser.h>
#include <libxml/HTMLtree.h>

/*
 * pseudo flag for the unification of HTML and XML tests
 */
#define XML_PARSE_HTML 1 << 24
#endif

#if defined(LIBXML_THREAD_ENABLED) && defined(LIBXML_CATALOG_ENABLED)
#include <libxml/globals.h>
#include <libxml/threads.h>
#include <libxml/parser.h>
#include <libxml/catalog.h>
#include <string.h>
#endif

/*
 * O_BINARY is just for Windows compatibility - if it isn't defined
 * on this system, avoid any compilation error
 */
#ifdef	O_BINARY
#define RD_FLAGS	O_RDONLY | O_BINARY
#define WR_FLAGS	O_WRONLY | O_CREAT | O_TRUNC | O_BINARY
#else
#define RD_FLAGS	O_RDONLY
#define WR_FLAGS	O_WRONLY | O_CREAT | O_TRUNC
#endif

typedef int (*functest) (const char *filename, const char *result,
                         const char *error, int options);

typedef struct testDesc testDesc;
typedef testDesc *testDescPtr;
struct testDesc {
    const char *desc; /* description of the test */
    functest    func; /* function implementing the test */
    const char *in;   /* glob to path for input files */
    const char *out;  /* output directory */
    const char *suffix;/* suffix for output files */
    const char *err;  /* suffix for error output files */
    int     options;  /* parser options for the test */
};

static int update_results = 0;
static int checkTestFile(const char *filename);

#if defined(_WIN32) && !defined(__CYGWIN__)

#include <windows.h>
#include <io.h>

typedef struct
{
      size_t gl_pathc;    /* Count of paths matched so far  */
      char **gl_pathv;    /* List of matched pathnames.  */
      size_t gl_offs;     /* Slots to reserve in 'gl_pathv'.  */
} glob_t;

#define GLOB_DOOFFS 0
static int glob(const char *pattern, ATTRIBUTE_UNUSED int flags,
                ATTRIBUTE_UNUSED int errfunc(const char *epath, int eerrno),
                glob_t *pglob) {
    glob_t *ret;
    WIN32_FIND_DATA FindFileData;
    HANDLE hFind;
    unsigned int nb_paths = 0;
    char directory[500];
    int len;

    if ((pattern == NULL) || (pglob == NULL)) return(-1);

    strncpy(directory, pattern, 499);
    for (len = strlen(directory);len >= 0;len--) {
        if (directory[len] == '/') {
	    len++;
	    directory[len] = 0;
	    break;
	}
    }
    if (len <= 0)
        len = 0;


    ret = pglob;
    memset(ret, 0, sizeof(glob_t));

    hFind = FindFirstFileA(pattern, &FindFileData);
    if (hFind == INVALID_HANDLE_VALUE)
        return(0);
    nb_paths = 20;
    ret->gl_pathv = (char **) malloc(nb_paths * sizeof(char *));
    if (ret->gl_pathv == NULL) {
	FindClose(hFind);
        return(-1);
    }
    strncpy(directory + len, FindFileData.cFileName, 499 - len);
    ret->gl_pathv[ret->gl_pathc] = strdup(directory);
    if (ret->gl_pathv[ret->gl_pathc] == NULL)
        goto done;
    ret->gl_pathc++;
    while(FindNextFileA(hFind, &FindFileData)) {
        if (FindFileData.cFileName[0] == '.')
	    continue;
        if (ret->gl_pathc + 2 > nb_paths) {
            char **tmp = realloc(ret->gl_pathv, nb_paths * 2 * sizeof(char *));
            if (tmp == NULL)
                break;
            ret->gl_pathv = tmp;
            nb_paths *= 2;
	}
	strncpy(directory + len, FindFileData.cFileName, 499 - len);
	ret->gl_pathv[ret->gl_pathc] = strdup(directory);
        if (ret->gl_pathv[ret->gl_pathc] == NULL)
            break;
        ret->gl_pathc++;
    }
    ret->gl_pathv[ret->gl_pathc] = NULL;

done:
    FindClose(hFind);
    return(0);
}



static void globfree(glob_t *pglob) {
    unsigned int i;
    if (pglob == NULL)
        return;

    for (i = 0;i < pglob->gl_pathc;i++) {
         if (pglob->gl_pathv[i] != NULL)
             free(pglob->gl_pathv[i]);
    }
}

#else
#include <glob.h>
#endif

/************************************************************************
 *									*
 *		Libxml2 specific routines				*
 *									*
 ************************************************************************/

static int nb_tests = 0;
static int nb_errors = 0;
static int nb_leaks = 0;
static int extraMemoryFromResolver = 0;

static int
fatalError(void) {
    fprintf(stderr, "Exitting tests on fatal error\n");
    exit(1);
}

/*
 * We need to trap calls to the resolver to not account memory for the catalog
 * which is shared to the current running test. We also don't want to have
 * network downloads modifying tests.
 */
static xmlParserInputPtr
testExternalEntityLoader(const char *URL, const char *ID,
			 xmlParserCtxtPtr ctxt) {
    xmlParserInputPtr ret;

    if (checkTestFile(URL)) {
	ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt);
    } else {
	int memused = xmlMemUsed();
	ret = xmlNoNetExternalEntityLoader(URL, ID, ctxt);
	extraMemoryFromResolver += xmlMemUsed() - memused;
    }

    return(ret);
}

/*
 * Trapping the error messages at the generic level to grab the equivalent of
 * stderr messages on CLI tools.
 */
static char testErrors[32769];
static int testErrorsSize = 0;

static void XMLCDECL
testErrorHandler(void *ctx  ATTRIBUTE_UNUSED, const char *msg, ...) {
    va_list args;
    int res;

    if (testErrorsSize >= 32768)
        return;
    va_start(args, msg);
    res = vsnprintf(&testErrors[testErrorsSize],
                    32768 - testErrorsSize,
		    msg, args);
    va_end(args);
    if (testErrorsSize + res >= 32768) {
        /* buffer is full */
	testErrorsSize = 32768;
	testErrors[testErrorsSize] = 0;
    } else {
        testErrorsSize += res;
    }
    testErrors[testErrorsSize] = 0;
}

static void XMLCDECL
channel(void *ctx  ATTRIBUTE_UNUSED, const char *msg, ...) {
    va_list args;
    int res;

    if (testErrorsSize >= 32768)
        return;
    va_start(args, msg);
    res = vsnprintf(&testErrors[testErrorsSize],
                    32768 - testErrorsSize,
		    msg, args);
    va_end(args);
    if (testErrorsSize + res >= 32768) {
        /* buffer is full */
	testErrorsSize = 32768;
	testErrors[testErrorsSize] = 0;
    } else {
        testErrorsSize += res;
    }
    testErrors[testErrorsSize] = 0;
}

/**
 * xmlParserPrintFileContext:
 * @input:  an xmlParserInputPtr input
 *
 * Displays current context within the input content for error tracking
 */

static void
xmlParserPrintFileContextInternal(xmlParserInputPtr input ,
		xmlGenericErrorFunc chanl, void *data ) {
    const xmlChar *cur, *base;
    unsigned int n, col;	/* GCC warns if signed, because compared with sizeof() */
    xmlChar  content[81]; /* space for 80 chars + line terminator */
    xmlChar *ctnt;

    if (input == NULL) return;
    cur = input->cur;
    base = input->base;
    /* skip backwards over any end-of-lines */
    while ((cur > base) && ((*(cur) == '\n') || (*(cur) == '\r'))) {
	cur--;
    }
    n = 0;
    /* search backwards for beginning-of-line (to max buff size) */
    while ((n++ < (sizeof(content)-1)) && (cur > base) &&
   (*(cur) != '\n') && (*(cur) != '\r'))
        cur--;
    if ((*(cur) == '\n') || (*(cur) == '\r')) cur++;
    /* calculate the error position in terms of the current position */
    col = input->cur - cur;
    /* search forward for end-of-line (to max buff size) */
    n = 0;
    ctnt = content;
    /* copy selected text to our buffer */
    while ((*cur != 0) && (*(cur) != '\n') &&
   (*(cur) != '\r') && (n < sizeof(content)-1)) {
		*ctnt++ = *cur++;
	n++;
    }
    *ctnt = 0;
    /* print out the selected text */
    chanl(data ,"%s\n", content);
    /* create blank line with problem pointer */
    n = 0;
    ctnt = content;
    /* (leave buffer space for pointer + line terminator) */
    while ((n<col) && (n++ < sizeof(content)-2) && (*ctnt != 0)) {
	if (*(ctnt) != '\t')
	    *(ctnt) = ' ';
	ctnt++;
    }
    *ctnt++ = '^';
    *ctnt = 0;
    chanl(data ,"%s\n", content);
}

static void
testStructuredErrorHandler(void *ctx  ATTRIBUTE_UNUSED, xmlErrorPtr err) {
    char *file = NULL;
    int line = 0;
    int code = -1;
    int domain;
    void *data = NULL;
    const char *str;
    const xmlChar *name = NULL;
    xmlNodePtr node;
    xmlErrorLevel level;
    xmlParserInputPtr input = NULL;
    xmlParserInputPtr cur = NULL;
    xmlParserCtxtPtr ctxt = NULL;

    if (err == NULL)
        return;

    file = err->file;
    line = err->line;
    code = err->code;
    domain = err->domain;
    level = err->level;
    node = err->node;
    if ((domain == XML_FROM_PARSER) || (domain == XML_FROM_HTML) ||
        (domain == XML_FROM_DTD) || (domain == XML_FROM_NAMESPACE) ||
	(domain == XML_FROM_IO) || (domain == XML_FROM_VALID)) {
	ctxt = err->ctxt;
    }
    str = err->message;

    if (code == XML_ERR_OK)
        return;

    if ((node != NULL) && (node->type == XML_ELEMENT_NODE))
        name = node->name;

    /*
     * Maintain the compatibility with the legacy error handling
     */
    if (ctxt != NULL) {
        input = ctxt->input;
        if ((input != NULL) && (input->filename == NULL) &&
            (ctxt->inputNr > 1)) {
            cur = input;
            input = ctxt->inputTab[ctxt->inputNr - 2];
        }
        if (input != NULL) {
            if (input->filename)
                channel(data, "%s:%d: ", input->filename, input->line);
            else if ((line != 0) && (domain == XML_FROM_PARSER))
                channel(data, "Entity: line %d: ", input->line);
        }
    } else {
        if (file != NULL)
            channel(data, "%s:%d: ", file, line);
        else if ((line != 0) && (domain == XML_FROM_PARSER))
            channel(data, "Entity: line %d: ", line);
    }
    if (name != NULL) {
        channel(data, "element %s: ", name);
    }
    if (code == XML_ERR_OK)
        return;
    switch (domain) {
        case XML_FROM_PARSER:
            channel(data, "parser ");
            break;
        case XML_FROM_NAMESPACE:
            channel(data, "namespace ");
            break;
        case XML_FROM_DTD:
        case XML_FROM_VALID:
            channel(data, "validity ");
            break;
        case XML_FROM_HTML:
            channel(data, "HTML parser ");
            break;
        case XML_FROM_MEMORY:
            channel(data, "memory ");
            break;
        case XML_FROM_OUTPUT:
            channel(data, "output ");
            break;
        case XML_FROM_IO:
            channel(data, "I/O ");
            break;
        case XML_FROM_XINCLUDE:
            channel(data, "XInclude ");
            break;
        case XML_FROM_XPATH:
            channel(data, "XPath ");
            break;
        case XML_FROM_XPOINTER:
            channel(data, "parser ");
            break;
        case XML_FROM_REGEXP:
            channel(data, "regexp ");
            break;
        case XML_FROM_MODULE:
            channel(data, "module ");
            break;
        case XML_FROM_SCHEMASV:
            channel(data, "Schemas validity ");
            break;
        case XML_FROM_SCHEMASP:
            channel(data, "Schemas parser ");
            break;
        case XML_FROM_RELAXNGP:
            channel(data, "Relax-NG parser ");
            break;
        case XML_FROM_RELAXNGV:
            channel(data, "Relax-NG validity ");
            break;
        case XML_FROM_CATALOG:
            channel(data, "Catalog ");
            break;
        case XML_FROM_C14N:
            channel(data, "C14N ");
            break;
        case XML_FROM_XSLT:
            channel(data, "XSLT ");
            break;
        default:
            break;
    }
    if (code == XML_ERR_OK)
        return;
    switch (level) {
        case XML_ERR_NONE:
            channel(data, ": ");
            break;
        case XML_ERR_WARNING:
            channel(data, "warning : ");
            break;
        case XML_ERR_ERROR:
            channel(data, "error : ");
            break;
        case XML_ERR_FATAL:
            channel(data, "error : ");
            break;
    }
    if (code == XML_ERR_OK)
        return;
    if (str != NULL) {
        int len;
	len = xmlStrlen((const xmlChar *)str);
	if ((len > 0) && (str[len - 1] != '\n'))
	    channel(data, "%s\n", str);
	else
	    channel(data, "%s", str);
    } else {
        channel(data, "%s\n", "out of memory error");
    }
    if (code == XML_ERR_OK)
        return;

    if (ctxt != NULL) {
        xmlParserPrintFileContextInternal(input, channel, data);
        if (cur != NULL) {
            if (cur->filename)
                channel(data, "%s:%d: \n", cur->filename, cur->line);
            else if ((line != 0) && (domain == XML_FROM_PARSER))
                channel(data, "Entity: line %d: \n", cur->line);
            xmlParserPrintFileContextInternal(cur, channel, data);
        }
    }
    if ((domain == XML_FROM_XPATH) && (err->str1 != NULL) &&
        (err->int1 < 100) &&
	(err->int1 < xmlStrlen((const xmlChar *)err->str1))) {
	xmlChar buf[150];
	int i;

	channel(data, "%s\n", err->str1);
	for (i=0;i < err->int1;i++)
	     buf[i] = ' ';
	buf[i++] = '^';
	buf[i] = 0;
	channel(data, "%s\n", buf);
    }
}

static void
initializeLibxml2(void) {
    xmlGetWarningsDefaultValue = 0;
    xmlPedanticParserDefault(0);

    xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
    xmlInitParser();
    xmlSetExternalEntityLoader(testExternalEntityLoader);
    xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler);
#ifdef LIBXML_SCHEMAS_ENABLED
    xmlSchemaInitTypes();
    xmlRelaxNGInitTypes();
#endif
}


/************************************************************************
 *									*
 *		File name and path utilities				*
 *									*
 ************************************************************************/

static const char *baseFilename(const char *filename) {
    const char *cur;
    if (filename == NULL)
        return(NULL);
    cur = &filename[strlen(filename)];
    while ((cur > filename) && (*cur != '/'))
        cur--;
    if (*cur == '/')
        return(cur + 1);
    return(cur);
}

static char *resultFilename(const char *filename, const char *out,
                            const char *suffix) {
    const char *base;
    char res[500];
    char suffixbuff[500];

/*************
    if ((filename[0] == 't') && (filename[1] == 'e') &&
        (filename[2] == 's') && (filename[3] == 't') &&
	(filename[4] == '/'))
	filename = &filename[5];
 *************/

    base = baseFilename(filename);
    if (suffix == NULL)
        suffix = ".tmp";
    if (out == NULL)
        out = "";

    strncpy(suffixbuff,suffix,499);
#ifdef VMS
    if(strstr(base,".") && suffixbuff[0]=='.')
      suffixbuff[0]='_';
#endif

    if (snprintf(res, 499, "%s%s%s", out, base, suffixbuff) >= 499)
        res[499] = 0;
    return(strdup(res));
}

static int checkTestFile(const char *filename) {
    struct stat buf;

    if (stat(filename, &buf) == -1)
        return(0);

#if defined(_WIN32) && !defined(__CYGWIN__)
    if (!(buf.st_mode & _S_IFREG))
        return(0);
#else
    if (!S_ISREG(buf.st_mode))
        return(0);
#endif

    return(1);
}

static int compareFiles(const char *r1 /* temp */, const char *r2 /* result */) {
    int res1, res2;
    int fd1, fd2;
    char bytes1[4096];
    char bytes2[4096];

    if (update_results) {
        fd1 = open(r1, RD_FLAGS);
        if (fd1 < 0)
            return(-1);
        fd2 = open(r2, WR_FLAGS, 0644);
        if (fd2 < 0) {
            close(fd1);
            return(-1);
        }
        do {
            res1 = read(fd1, bytes1, 4096);
            if (res1 <= 0)
                break;
            res2 = write(fd2, bytes1, res1);
            if (res2 <= 0 || res2 != res1)
                break;
        } while (1);
        close(fd2);
        close(fd1);
        return(res1 != 0);
    }

    fd1 = open(r1, RD_FLAGS);
    if (fd1 < 0)
        return(-1);
    fd2 = open(r2, RD_FLAGS);
    if (fd2 < 0) {
        close(fd1);
        return(-1);
    }
    while (1) {
        res1 = read(fd1, bytes1, 4096);
        res2 = read(fd2, bytes2, 4096);
	if ((res1 != res2) || (res1 < 0)) {
	    close(fd1);
	    close(fd2);
	    return(1);
	}
	if (res1 == 0)
	    break;
	if (memcmp(bytes1, bytes2, res1) != 0) {
	    close(fd1);
	    close(fd2);
	    return(1);
	}
    }
    close(fd1);
    close(fd2);
    return(0);
}

static int compareFileMem(const char *filename, const char *mem, int size) {
    int res;
    int fd;
    char bytes[4096];
    int idx = 0;
    struct stat info;

    if (update_results) {
        fd = open(filename, WR_FLAGS, 0644);
        if (fd < 0) {
	    fprintf(stderr, "failed to open %s for writing", filename);
            return(-1);
	}
        res = write(fd, mem, size);
        close(fd);
        return(res != size);
    }

    if (stat(filename, &info) < 0) {
        fprintf(stderr, "failed to stat %s\n", filename);
	return(-1);
    }
    if (info.st_size != size) {
        fprintf(stderr, "file %s is %ld bytes, result is %d bytes\n",
	        filename, (long) info.st_size, size);
        return(-1);
    }
    fd = open(filename, RD_FLAGS);
    if (fd < 0) {
	fprintf(stderr, "failed to open %s for reading", filename);
        return(-1);
    }
    while (idx < size) {
        res = read(fd, bytes, 4096);
	if (res <= 0)
	    break;
	if (res + idx > size)
	    break;
	if (memcmp(bytes, &mem[idx], res) != 0) {
	    int ix;
	    for (ix=0; ix<res; ix++)
		if (bytes[ix] != mem[idx+ix])
			break;
	    fprintf(stderr,"Compare error at position %d\n", idx+ix);
	    close(fd);
	    return(1);
	}
	idx += res;
    }
    close(fd);
    if (idx != size) {
	fprintf(stderr,"Compare error index %d, size %d\n", idx, size);
    }
    return(idx != size);
}

static int loadMem(const char *filename, const char **mem, int *size) {
    int fd, res;
    struct stat info;
    char *base;
    int siz = 0;
    if (stat(filename, &info) < 0)
	return(-1);
    base = malloc(info.st_size + 1);
    if (base == NULL)
	return(-1);
    if ((fd = open(filename, RD_FLAGS)) < 0) {
        free(base);
	return(-1);
    }
    while ((res = read(fd, &base[siz], info.st_size - siz)) > 0) {
        siz += res;
    }
    close(fd);
#if !defined(_WIN32)
    if (siz != info.st_size) {
        free(base);
	return(-1);
    }
#endif
    base[siz] = 0;
    *mem = base;
    *size = siz;
    return(0);
}

static int unloadMem(const char *mem) {
    free((char *)mem);
    return(0);
}

/************************************************************************
 *									*
 *		Tests implementations					*
 *									*
 ************************************************************************/

/************************************************************************
 *									*
 *		Parse to SAX based tests				*
 *									*
 ************************************************************************/

static FILE *SAXdebug = NULL;

/*
 * empty SAX block
 */
static xmlSAXHandler emptySAXHandlerStruct = {
    NULL, /* internalSubset */
    NULL, /* isStandalone */
    NULL, /* hasInternalSubset */
    NULL, /* hasExternalSubset */
    NULL, /* resolveEntity */
    NULL, /* getEntity */
    NULL, /* entityDecl */
    NULL, /* notationDecl */
    NULL, /* attributeDecl */
    NULL, /* elementDecl */
    NULL, /* unparsedEntityDecl */
    NULL, /* setDocumentLocator */
    NULL, /* startDocument */
    NULL, /* endDocument */
    NULL, /* startElement */
    NULL, /* endElement */
    NULL, /* reference */
    NULL, /* characters */
    NULL, /* ignorableWhitespace */
    NULL, /* processingInstruction */
    NULL, /* comment */
    NULL, /* xmlParserWarning */
    NULL, /* xmlParserError */
    NULL, /* xmlParserError */
    NULL, /* getParameterEntity */
    NULL, /* cdataBlock; */
    NULL, /* externalSubset; */
    1,
    NULL,
    NULL, /* startElementNs */
    NULL, /* endElementNs */
    NULL  /* xmlStructuredErrorFunc */
};

static xmlSAXHandlerPtr emptySAXHandler = &emptySAXHandlerStruct;
static int callbacks = 0;
static int quiet = 0;

/**
 * isStandaloneDebug:
 * @ctxt:  An XML parser context
 *
 * Is this document tagged standalone ?
 *
 * Returns 1 if true
 */
static int
isStandaloneDebug(void *ctx ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return(0);
    fprintf(SAXdebug, "SAX.isStandalone()\n");
    return(0);
}

/**
 * hasInternalSubsetDebug:
 * @ctxt:  An XML parser context
 *
 * Does this document has an internal subset
 *
 * Returns 1 if true
 */
static int
hasInternalSubsetDebug(void *ctx ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return(0);
    fprintf(SAXdebug, "SAX.hasInternalSubset()\n");
    return(0);
}

/**
 * hasExternalSubsetDebug:
 * @ctxt:  An XML parser context
 *
 * Does this document has an external subset
 *
 * Returns 1 if true
 */
static int
hasExternalSubsetDebug(void *ctx ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return(0);
    fprintf(SAXdebug, "SAX.hasExternalSubset()\n");
    return(0);
}

/**
 * internalSubsetDebug:
 * @ctxt:  An XML parser context
 *
 * Does this document has an internal subset
 */
static void
internalSubsetDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name,
	       const xmlChar *ExternalID, const xmlChar *SystemID)
{
    callbacks++;
    if (quiet)
	return;
    if (name == NULL)
        name = BAD_CAST "(null)";
    fprintf(SAXdebug, "SAX.internalSubset(%s,", name);
    if (ExternalID == NULL)
	fprintf(SAXdebug, " ,");
    else
	fprintf(SAXdebug, " %s,", ExternalID);
    if (SystemID == NULL)
	fprintf(SAXdebug, " )\n");
    else
	fprintf(SAXdebug, " %s)\n", SystemID);
}

/**
 * externalSubsetDebug:
 * @ctxt:  An XML parser context
 *
 * Does this document has an external subset
 */
static void
externalSubsetDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name,
	       const xmlChar *ExternalID, const xmlChar *SystemID)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.externalSubset(%s,", name);
    if (ExternalID == NULL)
	fprintf(SAXdebug, " ,");
    else
	fprintf(SAXdebug, " %s,", ExternalID);
    if (SystemID == NULL)
	fprintf(SAXdebug, " )\n");
    else
	fprintf(SAXdebug, " %s)\n", SystemID);
}

/**
 * resolveEntityDebug:
 * @ctxt:  An XML parser context
 * @publicId: The public ID of the entity
 * @systemId: The system ID of the entity
 *
 * Special entity resolver, better left to the parser, it has
 * more context than the application layer.
 * The default behaviour is to NOT resolve the entities, in that case
 * the ENTITY_REF nodes are built in the structure (and the parameter
 * values).
 *
 * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour.
 */
static xmlParserInputPtr
resolveEntityDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *publicId, const xmlChar *systemId)
{
    callbacks++;
    if (quiet)
	return(NULL);
    /* xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) ctx; */


    fprintf(SAXdebug, "SAX.resolveEntity(");
    if (publicId != NULL)
	fprintf(SAXdebug, "%s", (char *)publicId);
    else
	fprintf(SAXdebug, " ");
    if (systemId != NULL)
	fprintf(SAXdebug, ", %s)\n", (char *)systemId);
    else
	fprintf(SAXdebug, ", )\n");
/*********
    if (systemId != NULL) {
        return(xmlNewInputFromFile(ctxt, (char *) systemId));
    }
 *********/
    return(NULL);
}

/**
 * getEntityDebug:
 * @ctxt:  An XML parser context
 * @name: The entity name
 *
 * Get an entity by name
 *
 * Returns the xmlParserInputPtr if inlined or NULL for DOM behaviour.
 */
static xmlEntityPtr
getEntityDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name)
{
    callbacks++;
    if (quiet)
	return(NULL);
    fprintf(SAXdebug, "SAX.getEntity(%s)\n", name);
    return(NULL);
}

/**
 * getParameterEntityDebug:
 * @ctxt:  An XML parser context
 * @name: The entity name
 *
 * Get a parameter entity by name
 *
 * Returns the xmlParserInputPtr
 */
static xmlEntityPtr
getParameterEntityDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name)
{
    callbacks++;
    if (quiet)
	return(NULL);
    fprintf(SAXdebug, "SAX.getParameterEntity(%s)\n", name);
    return(NULL);
}


/**
 * entityDeclDebug:
 * @ctxt:  An XML parser context
 * @name:  the entity name
 * @type:  the entity type
 * @publicId: The public ID of the entity
 * @systemId: The system ID of the entity
 * @content: the entity value (without processing).
 *
 * An entity definition has been parsed
 */
static void
entityDeclDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name, int type,
          const xmlChar *publicId, const xmlChar *systemId, xmlChar *content)
{
const xmlChar *nullstr = BAD_CAST "(null)";
    /* not all libraries handle printing null pointers nicely */
    if (publicId == NULL)
        publicId = nullstr;
    if (systemId == NULL)
        systemId = nullstr;
    if (content == NULL)
        content = (xmlChar *)nullstr;
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.entityDecl(%s, %d, %s, %s, %s)\n",
            name, type, publicId, systemId, content);
}

/**
 * attributeDeclDebug:
 * @ctxt:  An XML parser context
 * @name:  the attribute name
 * @type:  the attribute type
 *
 * An attribute definition has been parsed
 */
static void
attributeDeclDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar * elem,
                   const xmlChar * name, int type, int def,
                   const xmlChar * defaultValue, xmlEnumerationPtr tree)
{
    callbacks++;
    if (quiet)
        return;
    if (defaultValue == NULL)
        fprintf(SAXdebug, "SAX.attributeDecl(%s, %s, %d, %d, NULL, ...)\n",
                elem, name, type, def);
    else
        fprintf(SAXdebug, "SAX.attributeDecl(%s, %s, %d, %d, %s, ...)\n",
                elem, name, type, def, defaultValue);
    xmlFreeEnumeration(tree);
}

/**
 * elementDeclDebug:
 * @ctxt:  An XML parser context
 * @name:  the element name
 * @type:  the element type
 * @content: the element value (without processing).
 *
 * An element definition has been parsed
 */
static void
elementDeclDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name, int type,
	    xmlElementContentPtr content ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.elementDecl(%s, %d, ...)\n",
            name, type);
}

/**
 * notationDeclDebug:
 * @ctxt:  An XML parser context
 * @name: The name of the notation
 * @publicId: The public ID of the entity
 * @systemId: The system ID of the entity
 *
 * What to do when a notation declaration has been parsed.
 */
static void
notationDeclDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name,
	     const xmlChar *publicId, const xmlChar *systemId)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.notationDecl(%s, %s, %s)\n",
            (char *) name, (char *) publicId, (char *) systemId);
}

/**
 * unparsedEntityDeclDebug:
 * @ctxt:  An XML parser context
 * @name: The name of the entity
 * @publicId: The public ID of the entity
 * @systemId: The system ID of the entity
 * @notationName: the name of the notation
 *
 * What to do when an unparsed entity declaration is parsed
 */
static void
unparsedEntityDeclDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name,
		   const xmlChar *publicId, const xmlChar *systemId,
		   const xmlChar *notationName)
{
const xmlChar *nullstr = BAD_CAST "(null)";

    if (publicId == NULL)
        publicId = nullstr;
    if (systemId == NULL)
        systemId = nullstr;
    if (notationName == NULL)
        notationName = nullstr;
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.unparsedEntityDecl(%s, %s, %s, %s)\n",
            (char *) name, (char *) publicId, (char *) systemId,
	    (char *) notationName);
}

/**
 * setDocumentLocatorDebug:
 * @ctxt:  An XML parser context
 * @loc: A SAX Locator
 *
 * Receive the document locator at startup, actually xmlDefaultSAXLocator
 * Everything is available on the context, so this is useless in our case.
 */
static void
setDocumentLocatorDebug(void *ctx ATTRIBUTE_UNUSED, xmlSAXLocatorPtr loc ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.setDocumentLocator()\n");
}

/**
 * startDocumentDebug:
 * @ctxt:  An XML parser context
 *
 * called when the document start being processed.
 */
static void
startDocumentDebug(void *ctx ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.startDocument()\n");
}

/**
 * endDocumentDebug:
 * @ctxt:  An XML parser context
 *
 * called when the document end has been detected.
 */
static void
endDocumentDebug(void *ctx ATTRIBUTE_UNUSED)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.endDocument()\n");
}

/**
 * startElementDebug:
 * @ctxt:  An XML parser context
 * @name:  The element name
 *
 * called when an opening tag has been processed.
 */
static void
startElementDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name, const xmlChar **atts)
{
    int i;

    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.startElement(%s", (char *) name);
    if (atts != NULL) {
        for (i = 0;(atts[i] != NULL);i++) {
	    fprintf(SAXdebug, ", %s='", atts[i++]);
	    if (atts[i] != NULL)
	        fprintf(SAXdebug, "%s'", atts[i]);
	}
    }
    fprintf(SAXdebug, ")\n");
}

/**
 * endElementDebug:
 * @ctxt:  An XML parser context
 * @name:  The element name
 *
 * called when the end of an element has been detected.
 */
static void
endElementDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.endElement(%s)\n", (char *) name);
}

/**
 * charactersDebug:
 * @ctxt:  An XML parser context
 * @ch:  a xmlChar string
 * @len: the number of xmlChar
 *
 * receiving some chars from the parser.
 * Question: how much at a time ???
 */
static void
charactersDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *ch, int len)
{
    char output[40];
    int i;

    callbacks++;
    if (quiet)
	return;
    for (i = 0;(i<len) && (i < 30);i++)
	output[i] = ch[i];
    output[i] = 0;

    fprintf(SAXdebug, "SAX.characters(%s, %d)\n", output, len);
}

/**
 * referenceDebug:
 * @ctxt:  An XML parser context
 * @name:  The entity name
 *
 * called when an entity reference is detected.
 */
static void
referenceDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.reference(%s)\n", name);
}

/**
 * ignorableWhitespaceDebug:
 * @ctxt:  An XML parser context
 * @ch:  a xmlChar string
 * @start: the first char in the string
 * @len: the number of xmlChar
 *
 * receiving some ignorable whitespaces from the parser.
 * Question: how much at a time ???
 */
static void
ignorableWhitespaceDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *ch, int len)
{
    char output[40];
    int i;

    callbacks++;
    if (quiet)
	return;
    for (i = 0;(i<len) && (i < 30);i++)
	output[i] = ch[i];
    output[i] = 0;
    fprintf(SAXdebug, "SAX.ignorableWhitespace(%s, %d)\n", output, len);
}

/**
 * processingInstructionDebug:
 * @ctxt:  An XML parser context
 * @target:  the target name
 * @data: the PI data's
 * @len: the number of xmlChar
 *
 * A processing instruction has been parsed.
 */
static void
processingInstructionDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *target,
                      const xmlChar *data)
{
    callbacks++;
    if (quiet)
	return;
    if (data != NULL)
	fprintf(SAXdebug, "SAX.processingInstruction(%s, %s)\n",
		(char *) target, (char *) data);
    else
	fprintf(SAXdebug, "SAX.processingInstruction(%s, NULL)\n",
		(char *) target);
}

/**
 * cdataBlockDebug:
 * @ctx: the user data (XML parser context)
 * @value:  The pcdata content
 * @len:  the block length
 *
 * called when a pcdata block has been parsed
 */
static void
cdataBlockDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *value, int len)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.pcdata(%.20s, %d)\n",
	    (char *) value, len);
}

/**
 * commentDebug:
 * @ctxt:  An XML parser context
 * @value:  the comment content
 *
 * A comment has been parsed.
 */
static void
commentDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *value)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.comment(%s)\n", value);
}

/**
 * warningDebug:
 * @ctxt:  An XML parser context
 * @msg:  the message to display/transmit
 * @...:  extra parameters for the message display
 *
 * Display and format a warning messages, gives file, line, position and
 * extra parameters.
 */
static void XMLCDECL
warningDebug(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...)
{
    va_list args;

    callbacks++;
    if (quiet)
	return;
    va_start(args, msg);
    fprintf(SAXdebug, "SAX.warning: ");
    vfprintf(SAXdebug, msg, args);
    va_end(args);
}

/**
 * errorDebug:
 * @ctxt:  An XML parser context
 * @msg:  the message to display/transmit
 * @...:  extra parameters for the message display
 *
 * Display and format a error messages, gives file, line, position and
 * extra parameters.
 */
static void XMLCDECL
errorDebug(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...)
{
    va_list args;

    callbacks++;
    if (quiet)
	return;
    va_start(args, msg);
    fprintf(SAXdebug, "SAX.error: ");
    vfprintf(SAXdebug, msg, args);
    va_end(args);
}

/**
 * fatalErrorDebug:
 * @ctxt:  An XML parser context
 * @msg:  the message to display/transmit
 * @...:  extra parameters for the message display
 *
 * Display and format a fatalError messages, gives file, line, position and
 * extra parameters.
 */
static void XMLCDECL
fatalErrorDebug(void *ctx ATTRIBUTE_UNUSED, const char *msg, ...)
{
    va_list args;

    callbacks++;
    if (quiet)
	return;
    va_start(args, msg);
    fprintf(SAXdebug, "SAX.fatalError: ");
    vfprintf(SAXdebug, msg, args);
    va_end(args);
}

static xmlSAXHandler debugSAXHandlerStruct = {
    internalSubsetDebug,
    isStandaloneDebug,
    hasInternalSubsetDebug,
    hasExternalSubsetDebug,
    resolveEntityDebug,
    getEntityDebug,
    entityDeclDebug,
    notationDeclDebug,
    attributeDeclDebug,
    elementDeclDebug,
    unparsedEntityDeclDebug,
    setDocumentLocatorDebug,
    startDocumentDebug,
    endDocumentDebug,
    startElementDebug,
    endElementDebug,
    referenceDebug,
    charactersDebug,
    ignorableWhitespaceDebug,
    processingInstructionDebug,
    commentDebug,
    warningDebug,
    errorDebug,
    fatalErrorDebug,
    getParameterEntityDebug,
    cdataBlockDebug,
    externalSubsetDebug,
    1,
    NULL,
    NULL,
    NULL,
    NULL
};

static xmlSAXHandlerPtr debugSAXHandler = &debugSAXHandlerStruct;

/*
 * SAX2 specific callbacks
 */
/**
 * startElementNsDebug:
 * @ctxt:  An XML parser context
 * @name:  The element name
 *
 * called when an opening tag has been processed.
 */
static void
startElementNsDebug(void *ctx ATTRIBUTE_UNUSED,
                    const xmlChar *localname,
                    const xmlChar *prefix,
                    const xmlChar *URI,
		    int nb_namespaces,
		    const xmlChar **namespaces,
		    int nb_attributes,
		    int nb_defaulted,
		    const xmlChar **attributes)
{
    int i;

    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.startElementNs(%s", (char *) localname);
    if (prefix == NULL)
	fprintf(SAXdebug, ", NULL");
    else
	fprintf(SAXdebug, ", %s", (char *) prefix);
    if (URI == NULL)
	fprintf(SAXdebug, ", NULL");
    else
	fprintf(SAXdebug, ", '%s'", (char *) URI);
    fprintf(SAXdebug, ", %d", nb_namespaces);

    if (namespaces != NULL) {
        for (i = 0;i < nb_namespaces * 2;i++) {
	    fprintf(SAXdebug, ", xmlns");
	    if (namespaces[i] != NULL)
	        fprintf(SAXdebug, ":%s", namespaces[i]);
	    i++;
	    fprintf(SAXdebug, "='%s'", namespaces[i]);
	}
    }
    fprintf(SAXdebug, ", %d, %d", nb_attributes, nb_defaulted);
    if (attributes != NULL) {
        for (i = 0;i < nb_attributes * 5;i += 5) {
	    if (attributes[i + 1] != NULL)
		fprintf(SAXdebug, ", %s:%s='", attributes[i + 1], attributes[i]);
	    else
		fprintf(SAXdebug, ", %s='", attributes[i]);
	    fprintf(SAXdebug, "%.4s...', %d", attributes[i + 3],
		    (int)(attributes[i + 4] - attributes[i + 3]));
	}
    }
    fprintf(SAXdebug, ")\n");
}

/**
 * endElementDebug:
 * @ctxt:  An XML parser context
 * @name:  The element name
 *
 * called when the end of an element has been detected.
 */
static void
endElementNsDebug(void *ctx ATTRIBUTE_UNUSED,
                  const xmlChar *localname,
                  const xmlChar *prefix,
                  const xmlChar *URI)
{
    callbacks++;
    if (quiet)
	return;
    fprintf(SAXdebug, "SAX.endElementNs(%s", (char *) localname);
    if (prefix == NULL)
	fprintf(SAXdebug, ", NULL");
    else
	fprintf(SAXdebug, ", %s", (char *) prefix);
    if (URI == NULL)
	fprintf(SAXdebug, ", NULL)\n");
    else
	fprintf(SAXdebug, ", '%s')\n", (char *) URI);
}

static xmlSAXHandler debugSAX2HandlerStruct = {
    internalSubsetDebug,
    isStandaloneDebug,
    hasInternalSubsetDebug,
    hasExternalSubsetDebug,
    resolveEntityDebug,
    getEntityDebug,
    entityDeclDebug,
    notationDeclDebug,
    attributeDeclDebug,
    elementDeclDebug,
    unparsedEntityDeclDebug,
    setDocumentLocatorDebug,
    startDocumentDebug,
    endDocumentDebug,
    NULL,
    NULL,
    referenceDebug,
    charactersDebug,
    ignorableWhitespaceDebug,
    processingInstructionDebug,
    commentDebug,
    warningDebug,
    errorDebug,
    fatalErrorDebug,
    getParameterEntityDebug,
    cdataBlockDebug,
    externalSubsetDebug,
    XML_SAX2_MAGIC,
    NULL,
    startElementNsDebug,
    endElementNsDebug,
    NULL
};

static xmlSAXHandlerPtr debugSAX2Handler = &debugSAX2HandlerStruct;

#ifdef LIBXML_HTML_ENABLED
/**
 * htmlstartElementDebug:
 * @ctxt:  An XML parser context
 * @name:  The element name
 *
 * called when an opening tag has been processed.
 */
static void
htmlstartElementDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *name, const xmlChar **atts)
{
    int i;

    fprintf(SAXdebug, "SAX.startElement(%s", (char *) name);
    if (atts != NULL) {
        for (i = 0;(atts[i] != NULL);i++) {
	    fprintf(SAXdebug, ", %s", atts[i++]);
	    if (atts[i] != NULL) {
		unsigned char output[40];
		const unsigned char *att = atts[i];
		int outlen, attlen;
	        fprintf(SAXdebug, "='");
		while ((attlen = strlen((char*)att)) > 0) {
		    outlen = sizeof output - 1;
		    htmlEncodeEntities(output, &outlen, att, &attlen, '\'');
		    output[outlen] = 0;
		    fprintf(SAXdebug, "%s", (char *) output);
		    att += attlen;
		}
		fprintf(SAXdebug, "'");
	    }
	}
    }
    fprintf(SAXdebug, ")\n");
}

/**
 * htmlcharactersDebug:
 * @ctxt:  An XML parser context
 * @ch:  a xmlChar string
 * @len: the number of xmlChar
 *
 * receiving some chars from the parser.
 * Question: how much at a time ???
 */
static void
htmlcharactersDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *ch, int len)
{
    unsigned char output[40];
    int inlen = len, outlen = 30;

    htmlEncodeEntities(output, &outlen, ch, &inlen, 0);
    output[outlen] = 0;

    fprintf(SAXdebug, "SAX.characters(%s, %d)\n", output, len);
}

/**
 * htmlcdataDebug:
 * @ctxt:  An XML parser context
 * @ch:  a xmlChar string
 * @len: the number of xmlChar
 *
 * receiving some cdata chars from the parser.
 * Question: how much at a time ???
 */
static void
htmlcdataDebug(void *ctx ATTRIBUTE_UNUSED, const xmlChar *ch, int len)
{
    unsigned char output[40];
    int inlen = len, outlen = 30;

    htmlEncodeEntities(output, &outlen, ch, &inlen, 0);
    output[outlen] = 0;

    fprintf(SAXdebug, "SAX.cdata(%s, %d)\n", output, len);
}

static xmlSAXHandler debugHTMLSAXHandlerStruct = {
    internalSubsetDebug,
    isStandaloneDebug,
    hasInternalSubsetDebug,
    hasExternalSubsetDebug,
    resolveEntityDebug,
    getEntityDebug,
    entityDeclDebug,
    notationDeclDebug,
    attributeDeclDebug,
    elementDeclDebug,
    unparsedEntityDeclDebug,
    setDocumentLocatorDebug,
    startDocumentDebug,
    endDocumentDebug,
    htmlstartElementDebug,
    endElementDebug,
    referenceDebug,
    htmlcharactersDebug,
    ignorableWhitespaceDebug,
    processingInstructionDebug,
    commentDebug,
    warningDebug,
    errorDebug,
    fatalErrorDebug,
    getParameterEntityDebug,
    htmlcdataDebug,
    externalSubsetDebug,
    1,
    NULL,
    NULL,
    NULL,
    NULL
};

static xmlSAXHandlerPtr debugHTMLSAXHandler = &debugHTMLSAXHandlerStruct;
#endif /* LIBXML_HTML_ENABLED */

/**
 * saxParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file using the SAX API and check for errors.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
saxParseTest(const char *filename, const char *result,
             const char *err ATTRIBUTE_UNUSED,
             int options) {
    int ret;
    char *temp;

    nb_tests++;
    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "out of memory\n");
        fatalError();
    }
    SAXdebug = fopen(temp, "wb");
    if (SAXdebug == NULL) {
        fprintf(stderr, "Failed to write to %s\n", temp);
	free(temp);
	return(-1);
    }

    /* for SAX we really want the callbacks though the context handlers */
    xmlSetStructuredErrorFunc(NULL, NULL);
    xmlSetGenericErrorFunc(NULL, testErrorHandler);

#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
	htmlSAXParseFile(filename, NULL, emptySAXHandler, NULL);
	ret = 0;
    } else
#endif
    {
        xmlParserCtxtPtr ctxt = xmlCreateFileParserCtxt(filename);
        memcpy(ctxt->sax, emptySAXHandler, sizeof(xmlSAXHandler));
        xmlCtxtUseOptions(ctxt, options);
        xmlParseDocument(ctxt);
        ret = ctxt->wellFormed ? 0 : ctxt->errNo;
        xmlFreeDoc(ctxt->myDoc);
        xmlFreeParserCtxt(ctxt);
    }
    if (ret == XML_WAR_UNDECLARED_ENTITY) {
        fprintf(SAXdebug, "xmlSAXUserParseFile returned error %d\n", ret);
        ret = 0;
    }
    if (ret != 0) {
        fprintf(stderr, "Failed to parse %s\n", filename);
	ret = 1;
	goto done;
    }
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
	htmlSAXParseFile(filename, NULL, debugHTMLSAXHandler, NULL);
	ret = 0;
    } else
#endif
    {
        xmlParserCtxtPtr ctxt = xmlCreateFileParserCtxt(filename);
        if (options & XML_PARSE_SAX1) {
            memcpy(ctxt->sax, debugSAXHandler, sizeof(xmlSAXHandler));
            options -= XML_PARSE_SAX1;
        } else {
            memcpy(ctxt->sax, debugSAX2Handler, sizeof(xmlSAXHandler));
        }
        xmlCtxtUseOptions(ctxt, options);
        xmlParseDocument(ctxt);
        ret = ctxt->wellFormed ? 0 : ctxt->errNo;
        xmlFreeDoc(ctxt->myDoc);
        xmlFreeParserCtxt(ctxt);
    }
    if (ret == XML_WAR_UNDECLARED_ENTITY) {
        fprintf(SAXdebug, "xmlSAXUserParseFile returned error %d\n", ret);
        ret = 0;
    }
    fclose(SAXdebug);
    if (compareFiles(temp, result)) {
        fprintf(stderr, "Got a difference for %s\n", filename);
        ret = 1;
    }

done:
    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }

    /* switch back to structured error handling */
    xmlSetGenericErrorFunc(NULL, NULL);
    xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler);

    return(ret);
}

/************************************************************************
 *									*
 *		Parse to tree based tests				*
 *									*
 ************************************************************************/
/**
 * oldParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages: unused
 *
 * Parse a file using the old xmlParseFile API, then serialize back
 * reparse the result and serialize again, then check for deviation
 * in serialization.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
oldParseTest(const char *filename, const char *result,
             const char *err ATTRIBUTE_UNUSED,
	     int options ATTRIBUTE_UNUSED) {
    xmlDocPtr doc;
    char *temp;
    int res = 0;

    nb_tests++;
    /*
     * base of the test, parse with the old API
     */
#ifdef LIBXML_SAX1_ENABLED
    doc = xmlParseFile(filename);
#else
    doc = xmlReadFile(filename, NULL, 0);
#endif
    if (doc == NULL)
        return(1);
    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "out of memory\n");
        fatalError();
    }
    xmlSaveFile(temp, doc);
    if (compareFiles(temp, result)) {
        res = 1;
    }
    xmlFreeDoc(doc);

    /*
     * Parse the saved result to make sure the round trip is okay
     */
#ifdef LIBXML_SAX1_ENABLED
    doc = xmlParseFile(temp);
#else
    doc = xmlReadFile(temp, NULL, 0);
#endif
    if (doc == NULL)
        return(1);
    xmlSaveFile(temp, doc);
    if (compareFiles(temp, result)) {
        res = 1;
    }
    xmlFreeDoc(doc);

    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }
    return(res);
}

#ifdef LIBXML_PUSH_ENABLED
/**
 * pushParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages: unused
 *
 * Parse a file using the Push API, then serialize back
 * to check for content.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
pushParseTest(const char *filename, const char *result,
             const char *err ATTRIBUTE_UNUSED,
	     int options) {
    xmlParserCtxtPtr ctxt;
    xmlDocPtr doc;
    const char *base;
    int size, res;
    int cur = 0;
    int chunkSize = 4;

    nb_tests++;
    /*
     * load the document in memory and work from there.
     */
    if (loadMem(filename, &base, &size) != 0) {
        fprintf(stderr, "Failed to load %s\n", filename);
	return(-1);
    }

    if (chunkSize > size)
        chunkSize = size;

#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML)
	ctxt = htmlCreatePushParserCtxt(NULL, NULL, base + cur, chunkSize, filename,
	                                XML_CHAR_ENCODING_NONE);
    else
#endif
    ctxt = xmlCreatePushParserCtxt(NULL, NULL, base + cur, chunkSize, filename);
    xmlCtxtUseOptions(ctxt, options);
    cur += chunkSize;
    chunkSize = 1024;
    do {
        if (cur + chunkSize >= size) {
#ifdef LIBXML_HTML_ENABLED
	    if (options & XML_PARSE_HTML)
		htmlParseChunk(ctxt, base + cur, size - cur, 1);
	    else
#endif
	    xmlParseChunk(ctxt, base + cur, size - cur, 1);
	    break;
	} else {
#ifdef LIBXML_HTML_ENABLED
	    if (options & XML_PARSE_HTML)
		htmlParseChunk(ctxt, base + cur, chunkSize, 0);
	    else
#endif
	    xmlParseChunk(ctxt, base + cur, chunkSize, 0);
	    cur += chunkSize;
	}
    } while (cur < size);
    doc = ctxt->myDoc;
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML)
        res = 1;
    else
#endif
    res = ctxt->wellFormed;
    xmlFreeParserCtxt(ctxt);
    free((char *)base);
    if (!res) {
	xmlFreeDoc(doc);
	fprintf(stderr, "Failed to parse %s\n", filename);
	return(-1);
    }
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML)
	htmlDocDumpMemory(doc, (xmlChar **) &base, &size);
    else
#endif
    xmlDocDumpMemory(doc, (xmlChar **) &base, &size);
    xmlFreeDoc(doc);
    res = compareFileMem(result, base, size);
    if ((base == NULL) || (res != 0)) {
	if (base != NULL)
	    xmlFree((char *)base);
        fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	return(-1);
    }
    xmlFree((char *)base);
    if (err != NULL) {
	res = compareFileMem(err, testErrors, testErrorsSize);
	if (res != 0) {
	    fprintf(stderr, "Error for %s failed\n", filename);
	    return(-1);
	}
    }
    return(0);
}
#endif

/**
 * memParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages: unused
 *
 * Parse a file using the old xmlReadMemory API, then serialize back
 * reparse the result and serialize again, then check for deviation
 * in serialization.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
memParseTest(const char *filename, const char *result,
             const char *err ATTRIBUTE_UNUSED,
	     int options ATTRIBUTE_UNUSED) {
    xmlDocPtr doc;
    const char *base;
    int size, res;

    nb_tests++;
    /*
     * load and parse the memory
     */
    if (loadMem(filename, &base, &size) != 0) {
        fprintf(stderr, "Failed to load %s\n", filename);
	return(-1);
    }

    doc = xmlReadMemory(base, size, filename, NULL, 0);
    unloadMem(base);
    if (doc == NULL) {
        return(1);
    }
    xmlDocDumpMemory(doc, (xmlChar **) &base, &size);
    xmlFreeDoc(doc);
    res = compareFileMem(result, base, size);
    if ((base == NULL) || (res != 0)) {
	if (base != NULL)
	    xmlFree((char *)base);
        fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	return(-1);
    }
    xmlFree((char *)base);
    return(0);
}

/**
 * noentParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages: unused
 *
 * Parse a file with entity resolution, then serialize back
 * reparse the result and serialize again, then check for deviation
 * in serialization.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
noentParseTest(const char *filename, const char *result,
               const char *err  ATTRIBUTE_UNUSED,
	       int options) {
    xmlDocPtr doc;
    char *temp;
    int res = 0;

    nb_tests++;
    /*
     * base of the test, parse with the old API
     */
    doc = xmlReadFile(filename, NULL, options);
    if (doc == NULL)
        return(1);
    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    xmlSaveFile(temp, doc);
    if (compareFiles(temp, result)) {
        res = 1;
    }
    xmlFreeDoc(doc);

    /*
     * Parse the saved result to make sure the round trip is okay
     */
    doc = xmlReadFile(filename, NULL, options);
    if (doc == NULL)
        return(1);
    xmlSaveFile(temp, doc);
    if (compareFiles(temp, result)) {
        res = 1;
    }
    xmlFreeDoc(doc);

    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }
    return(res);
}

/**
 * errParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file using the xmlReadFile API and check for errors.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
errParseTest(const char *filename, const char *result, const char *err,
             int options) {
    xmlDocPtr doc;
    const char *base = NULL;
    int size, res = 0;

    nb_tests++;
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
        doc = htmlReadFile(filename, NULL, options);
    } else
#endif
#ifdef LIBXML_XINCLUDE_ENABLED
    if (options & XML_PARSE_XINCLUDE) {
	doc = xmlReadFile(filename, NULL, options);
	xmlXIncludeProcessFlags(doc, options);
    } else
#endif
    {
	xmlGetWarningsDefaultValue = 1;
	doc = xmlReadFile(filename, NULL, options);
    }
    xmlGetWarningsDefaultValue = 0;
    if (result) {
	if (doc == NULL) {
	    base = "";
	    size = 0;
	} else {
#ifdef LIBXML_HTML_ENABLED
	    if (options & XML_PARSE_HTML) {
		htmlDocDumpMemory(doc, (xmlChar **) &base, &size);
	    } else
#endif
	    xmlDocDumpMemory(doc, (xmlChar **) &base, &size);
	}
	res = compareFileMem(result, base, size);
	if (res != 0) {
	    fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	    return(-1);
	}
    }
    if (doc != NULL) {
	if (base != NULL)
	    xmlFree((char *)base);
	xmlFreeDoc(doc);
    }
    if (err != NULL) {
	res = compareFileMem(err, testErrors, testErrorsSize);
	if (res != 0) {
	    fprintf(stderr, "Error for %s failed\n", filename);
	    return(-1);
	}
    } else if (options & XML_PARSE_DTDVALID) {
        if (testErrorsSize != 0)
	    fprintf(stderr, "Validation for %s failed\n", filename);
    }

    return(0);
}

#ifdef LIBXML_READER_ENABLED
/************************************************************************
 *									*
 *		Reader based tests					*
 *									*
 ************************************************************************/

static void processNode(FILE *out, xmlTextReaderPtr reader) {
    const xmlChar *name, *value;
    int type, empty;

    type = xmlTextReaderNodeType(reader);
    empty = xmlTextReaderIsEmptyElement(reader);

    name = xmlTextReaderConstName(reader);
    if (name == NULL)
	name = BAD_CAST "--";

    value = xmlTextReaderConstValue(reader);


    fprintf(out, "%d %d %s %d %d",
	    xmlTextReaderDepth(reader),
	    type,
	    name,
	    empty,
	    xmlTextReaderHasValue(reader));
    if (value == NULL)
	fprintf(out, "\n");
    else {
	fprintf(out, " %s\n", value);
    }
}
static int
streamProcessTest(const char *filename, const char *result, const char *err,
                  xmlTextReaderPtr reader, const char *rng,
                  int options ATTRIBUTE_UNUSED) {
    int ret;
    char *temp = NULL;
    FILE *t = NULL;

    if (reader == NULL)
        return(-1);

    nb_tests++;
    if (result != NULL) {
	temp = resultFilename(filename, "", ".res");
	if (temp == NULL) {
	    fprintf(stderr, "Out of memory\n");
	    fatalError();
	}
	t = fopen(temp, "wb");
	if (t == NULL) {
	    fprintf(stderr, "Can't open temp file %s\n", temp);
	    free(temp);
	    return(-1);
	}
    }
#ifdef LIBXML_SCHEMAS_ENABLED
    if (rng != NULL) {
	ret = xmlTextReaderRelaxNGValidate(reader, rng);
	if (ret < 0) {
	    testErrorHandler(NULL, "Relax-NG schema %s failed to compile\n",
	                     rng);
	    fclose(t);
            if (temp != NULL) {
                unlink(temp);
                free(temp);
            }
	    return(0);
	}
    }
#endif
    xmlGetWarningsDefaultValue = 1;
    ret = xmlTextReaderRead(reader);
    while (ret == 1) {
	if ((t != NULL) && (rng == NULL))
	    processNode(t, reader);
        ret = xmlTextReaderRead(reader);
    }
    if (ret != 0) {
        testErrorHandler(NULL, "%s : failed to parse\n", filename);
    }
    if (rng != NULL) {
        if (xmlTextReaderIsValid(reader) != 1) {
	    testErrorHandler(NULL, "%s fails to validate\n", filename);
	} else {
	    testErrorHandler(NULL, "%s validates\n", filename);
	}
    }
    xmlGetWarningsDefaultValue = 0;
    if (t != NULL) {
        fclose(t);
	ret = compareFiles(temp, result);
        if (temp != NULL) {
            unlink(temp);
            free(temp);
        }
	if (ret) {
	    fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	    return(-1);
	}
    }
    if (err != NULL) {
	ret = compareFileMem(err, testErrors, testErrorsSize);
	if (ret != 0) {
	    fprintf(stderr, "Error for %s failed\n", filename);
	    printf("%s", testErrors);
	    return(-1);
	}
    }

    return(0);
}

/**
 * streamParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file using the reader API and check for errors.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
streamParseTest(const char *filename, const char *result, const char *err,
                int options) {
    xmlTextReaderPtr reader;
    int ret;

    reader = xmlReaderForFile(filename, NULL, options);
    ret = streamProcessTest(filename, result, err, reader, NULL, options);
    xmlFreeTextReader(reader);
    return(ret);
}

/**
 * walkerParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file using the walker, i.e. a reader built from a atree.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
walkerParseTest(const char *filename, const char *result, const char *err,
                int options) {
    xmlDocPtr doc;
    xmlTextReaderPtr reader;
    int ret;

    doc = xmlReadFile(filename, NULL, options);
    if (doc == NULL) {
        fprintf(stderr, "Failed to parse %s\n", filename);
	return(-1);
    }
    reader = xmlReaderWalker(doc);
    ret = streamProcessTest(filename, result, err, reader, NULL, options);
    xmlFreeTextReader(reader);
    xmlFreeDoc(doc);
    return(ret);
}

/**
 * streamMemParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file using the reader API from memory and check for errors.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
streamMemParseTest(const char *filename, const char *result, const char *err,
                   int options) {
    xmlTextReaderPtr reader;
    int ret;
    const char *base;
    int size;

    /*
     * load and parse the memory
     */
    if (loadMem(filename, &base, &size) != 0) {
        fprintf(stderr, "Failed to load %s\n", filename);
	return(-1);
    }
    reader = xmlReaderForMemory(base, size, filename, NULL, options);
    ret = streamProcessTest(filename, result, err, reader, NULL, options);
    free((char *)base);
    xmlFreeTextReader(reader);
    return(ret);
}
#endif

#ifdef LIBXML_XPATH_ENABLED
#ifdef LIBXML_DEBUG_ENABLED
/************************************************************************
 *									*
 *		XPath and XPointer based tests				*
 *									*
 ************************************************************************/

static FILE *xpathOutput;
static xmlDocPtr xpathDocument;

static void
ignoreGenericError(void *ctx ATTRIBUTE_UNUSED,
        const char *msg ATTRIBUTE_UNUSED, ...) {
}

static void
testXPath(const char *str, int xptr, int expr) {
    xmlGenericErrorFunc handler = ignoreGenericError;
    xmlXPathObjectPtr res;
    xmlXPathContextPtr ctxt;

    /* Don't print generic errors to stderr. */
    initGenericErrorDefaultFunc(&handler);

    nb_tests++;
#if defined(LIBXML_XPTR_ENABLED)
    if (xptr) {
	ctxt = xmlXPtrNewContext(xpathDocument, NULL, NULL);
	res = xmlXPtrEval(BAD_CAST str, ctxt);
    } else {
#endif
	ctxt = xmlXPathNewContext(xpathDocument);
	ctxt->node = xmlDocGetRootElement(xpathDocument);
	if (expr)
	    res = xmlXPathEvalExpression(BAD_CAST str, ctxt);
	else {
	    /* res = xmlXPathEval(BAD_CAST str, ctxt); */
	    xmlXPathCompExprPtr comp;

	    comp = xmlXPathCompile(BAD_CAST str);
	    if (comp != NULL) {
		res = xmlXPathCompiledEval(comp, ctxt);
		xmlXPathFreeCompExpr(comp);
	    } else
		res = NULL;
	}
#if defined(LIBXML_XPTR_ENABLED)
    }
#endif
    xmlXPathDebugDumpObject(xpathOutput, res, 0);
    xmlXPathFreeObject(res);
    xmlXPathFreeContext(ctxt);

    /* Reset generic error handler. */
    initGenericErrorDefaultFunc(NULL);
}

/**
 * xpathExprTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing XPath standalone expressions and evaluate them
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
xpathCommonTest(const char *filename, const char *result,
                int xptr, int expr) {
    FILE *input;
    char expression[5000];
    int len, ret = 0;
    char *temp;

    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    xpathOutput = fopen(temp, "wb");
    if (xpathOutput == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
        free(temp);
	return(-1);
    }

    input = fopen(filename, "rb");
    if (input == NULL) {
        xmlGenericError(xmlGenericErrorContext,
		"Cannot open %s for reading\n", filename);
        free(temp);
	return(-1);
    }
    while (fgets(expression, 4500, input) != NULL) {
	len = strlen(expression);
	len--;
	while ((len >= 0) &&
	       ((expression[len] == '\n') || (expression[len] == '\t') ||
		(expression[len] == '\r') || (expression[len] == ' '))) len--;
	expression[len + 1] = 0;
	if (len >= 0) {
	    fprintf(xpathOutput,
	            "\n========================\nExpression: %s\n",
		    expression) ;
	    testXPath(expression, xptr, expr);
	}
    }

    fclose(input);
    fclose(xpathOutput);
    if (result != NULL) {
	ret = compareFiles(temp, result);
	if (ret) {
	    fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	}
    }

    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }
    return(ret);
}

/**
 * xpathExprTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing XPath standalone expressions and evaluate them
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
xpathExprTest(const char *filename, const char *result,
              const char *err ATTRIBUTE_UNUSED,
              int options ATTRIBUTE_UNUSED) {
    return(xpathCommonTest(filename, result, 0, 1));
}

/**
 * xpathDocTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing XPath expressions and evaluate them against
 * a set of corresponding documents.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
xpathDocTest(const char *filename,
             const char *resul ATTRIBUTE_UNUSED,
             const char *err ATTRIBUTE_UNUSED,
             int options) {

    char pattern[500];
    char result[500];
    glob_t globbuf;
    size_t i;
    int ret = 0, res;

    xpathDocument = xmlReadFile(filename, NULL,
                                options | XML_PARSE_DTDATTR | XML_PARSE_NOENT);
    if (xpathDocument == NULL) {
        fprintf(stderr, "Failed to load %s\n", filename);
	return(-1);
    }

    res = snprintf(pattern, 499, "./test/XPath/tests/%s*",
            baseFilename(filename));
    if (res >= 499)
        pattern[499] = 0;
    globbuf.gl_offs = 0;
    glob(pattern, GLOB_DOOFFS, NULL, &globbuf);
    for (i = 0;i < globbuf.gl_pathc;i++) {
        res = snprintf(result, 499, "result/XPath/tests/%s",
	         baseFilename(globbuf.gl_pathv[i]));
        if (res >= 499)
            result[499] = 0;
	res = xpathCommonTest(globbuf.gl_pathv[i], &result[0], 0, 0);
	if (res != 0)
	    ret = res;
    }
    globfree(&globbuf);

    xmlFreeDoc(xpathDocument);
    return(ret);
}

#ifdef LIBXML_XPTR_ENABLED
/**
 * xptrDocTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing XPath expressions and evaluate them against
 * a set of corresponding documents.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
xptrDocTest(const char *filename,
            const char *resul ATTRIBUTE_UNUSED,
            const char *err ATTRIBUTE_UNUSED,
            int options) {

    char pattern[500];
    char result[500];
    glob_t globbuf;
    size_t i;
    int ret = 0, res;

    xpathDocument = xmlReadFile(filename, NULL,
                                options | XML_PARSE_DTDATTR | XML_PARSE_NOENT);
    if (xpathDocument == NULL) {
        fprintf(stderr, "Failed to load %s\n", filename);
	return(-1);
    }

    res = snprintf(pattern, 499, "./test/XPath/xptr/%s*",
            baseFilename(filename));
    if (res >= 499)
        pattern[499] = 0;
    globbuf.gl_offs = 0;
    glob(pattern, GLOB_DOOFFS, NULL, &globbuf);
    for (i = 0;i < globbuf.gl_pathc;i++) {
        res = snprintf(result, 499, "result/XPath/xptr/%s",
	         baseFilename(globbuf.gl_pathv[i]));
        if (res >= 499)
            result[499] = 0;
	res = xpathCommonTest(globbuf.gl_pathv[i], &result[0], 1, 0);
	if (res != 0)
	    ret = res;
    }
    globfree(&globbuf);

    xmlFreeDoc(xpathDocument);
    return(ret);
}
#endif /* LIBXML_XPTR_ENABLED */

/**
 * xmlidDocTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing xml:id and check for errors and verify
 * that XPath queries will work on them as expected.
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
xmlidDocTest(const char *filename,
             const char *result,
             const char *err,
             int options) {

    int res = 0;
    int ret = 0;
    char *temp;

    xpathDocument = xmlReadFile(filename, NULL,
                                options | XML_PARSE_DTDATTR | XML_PARSE_NOENT);
    if (xpathDocument == NULL) {
        fprintf(stderr, "Failed to load %s\n", filename);
	return(-1);
    }

    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    xpathOutput = fopen(temp, "wb");
    if (xpathOutput == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
        xmlFreeDoc(xpathDocument);
        free(temp);
	return(-1);
    }

    testXPath("id('bar')", 0, 0);

    fclose(xpathOutput);
    if (result != NULL) {
	ret = compareFiles(temp, result);
	if (ret) {
	    fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	    res = 1;
	}
    }

    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }
    xmlFreeDoc(xpathDocument);

    if (err != NULL) {
	ret = compareFileMem(err, testErrors, testErrorsSize);
	if (ret != 0) {
	    fprintf(stderr, "Error for %s failed\n", filename);
	    res = 1;
	}
    }
    return(res);
}

#endif /* LIBXML_DEBUG_ENABLED */
#endif /* XPATH */
/************************************************************************
 *									*
 *			URI based tests					*
 *									*
 ************************************************************************/

static void
handleURI(const char *str, const char *base, FILE *o) {
    int ret;
    xmlURIPtr uri;
    xmlChar *res = NULL;

    uri = xmlCreateURI();

    if (base == NULL) {
	ret = xmlParseURIReference(uri, str);
	if (ret != 0)
	    fprintf(o, "%s : error %d\n", str, ret);
	else {
	    xmlNormalizeURIPath(uri->path);
	    xmlPrintURI(o, uri);
	    fprintf(o, "\n");
	}
    } else {
	res = xmlBuildURI((xmlChar *)str, (xmlChar *) base);
	if (res != NULL) {
	    fprintf(o, "%s\n", (char *) res);
	}
	else
	    fprintf(o, "::ERROR::\n");
    }
    if (res != NULL)
	xmlFree(res);
    xmlFreeURI(uri);
}

/**
 * uriCommonTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing URI and check for errors
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
uriCommonTest(const char *filename,
             const char *result,
             const char *err,
             const char *base) {
    char *temp;
    FILE *o, *f;
    char str[1024];
    int res = 0, i, ret;

    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    o = fopen(temp, "wb");
    if (o == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
        free(temp);
	return(-1);
    }
    f = fopen(filename, "rb");
    if (f == NULL) {
	fprintf(stderr, "failed to open input file %s\n", filename);
	fclose(o);
        if (temp != NULL) {
            unlink(temp);
            free(temp);
        }
	return(-1);
    }

    while (1) {
	/*
	 * read one line in string buffer.
	 */
	if (fgets (&str[0], sizeof (str) - 1, f) == NULL)
	   break;

	/*
	 * remove the ending spaces
	 */
	i = strlen(str);
	while ((i > 0) &&
	       ((str[i - 1] == '\n') || (str[i - 1] == '\r') ||
		(str[i - 1] == ' ') || (str[i - 1] == '\t'))) {
	    i--;
	    str[i] = 0;
	}
	nb_tests++;
	handleURI(str, base, o);
    }

    fclose(f);
    fclose(o);

    if (result != NULL) {
	ret = compareFiles(temp, result);
	if (ret) {
	    fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	    res = 1;
	}
    }
    if (err != NULL) {
	ret = compareFileMem(err, testErrors, testErrorsSize);
	if (ret != 0) {
	    fprintf(stderr, "Error for %s failed\n", filename);
	    res = 1;
	}
    }

    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }
    return(res);
}

/**
 * uriParseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing URI and check for errors
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
uriParseTest(const char *filename,
             const char *result,
             const char *err,
             int options ATTRIBUTE_UNUSED) {
    return(uriCommonTest(filename, result, err, NULL));
}

/**
 * uriBaseTest:
 * @filename: the file to parse
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing URI, compose them against a fixed base and
 * check for errors
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
uriBaseTest(const char *filename,
             const char *result,
             const char *err,
             int options ATTRIBUTE_UNUSED) {
    return(uriCommonTest(filename, result, err,
                         "http://foo.com/path/to/index.html?orig#help"));
}

static int urip_success = 1;
static int urip_current = 0;
static const char *urip_testURLs[] = {
    "urip://example.com/a b.html",
    "urip://example.com/a%20b.html",
    "file:///path/to/a b.html",
    "file:///path/to/a%20b.html",
    "/path/to/a b.html",
    "/path/to/a%20b.html",
    "urip://example.com/r" "\xe9" "sum" "\xe9" ".html",
    "urip://example.com/test?a=1&b=2%263&c=4#foo",
    NULL
};
static const char *urip_rcvsURLs[] = {
    /* it is an URI the strings must be escaped */
    "urip://example.com/a%20b.html",
    /* check that % escaping is not broken */
    "urip://example.com/a%20b.html",
    /* it's an URI path the strings must be escaped */
    "file:///path/to/a%20b.html",
    /* check that % escaping is not broken */
    "file:///path/to/a%20b.html",
    /* this is not an URI, this is a path, so this should not be escaped */
    "/path/to/a b.html",
    /* check that paths with % are not broken */
    "/path/to/a%20b.html",
    /* out of context the encoding can't be guessed byte by byte conversion */
    "urip://example.com/r%E9sum%E9.html",
    /* verify we don't destroy URIs especially the query part */
    "urip://example.com/test?a=1&b=2%263&c=4#foo",
    NULL
};
static const char *urip_res = "<list/>";
static const char *urip_cur = NULL;
static int urip_rlen;

/**
 * uripMatch:
 * @URI: an URI to test
 *
 * Check for an urip: query
 *
 * Returns 1 if yes and 0 if another Input module should be used
 */
static int
uripMatch(const char * URI) {
    if ((URI == NULL) || (!strcmp(URI, "file:///etc/xml/catalog")))
        return(0);
    /* Verify we received the escaped URL */
    if (strcmp(urip_rcvsURLs[urip_current], URI))
	urip_success = 0;
    return(1);
}

/**
 * uripOpen:
 * @URI: an URI to test
 *
 * Return a pointer to the urip: query handler, in this example simply
 * the urip_current pointer...
 *
 * Returns an Input context or NULL in case or error
 */
static void *
uripOpen(const char * URI) {
    if ((URI == NULL) || (!strcmp(URI, "file:///etc/xml/catalog")))
        return(NULL);
    /* Verify we received the escaped URL */
    if (strcmp(urip_rcvsURLs[urip_current], URI))
	urip_success = 0;
    urip_cur = urip_res;
    urip_rlen = strlen(urip_res);
    return((void *) urip_cur);
}

/**
 * uripClose:
 * @context: the read context
 *
 * Close the urip: query handler
 *
 * Returns 0 or -1 in case of error
 */
static int
uripClose(void * context) {
    if (context == NULL) return(-1);
    urip_cur = NULL;
    urip_rlen = 0;
    return(0);
}

/**
 * uripRead:
 * @context: the read context
 * @buffer: where to store data
 * @len: number of bytes to read
 *
 * Implement an urip: query read.
 *
 * Returns the number of bytes read or -1 in case of error
 */
static int
uripRead(void * context, char * buffer, int len) {
   const char *ptr = (const char *) context;

   if ((context == NULL) || (buffer == NULL) || (len < 0))
       return(-1);

   if (len > urip_rlen) len = urip_rlen;
   memcpy(buffer, ptr, len);
   urip_rlen -= len;
   return(len);
}

static int
urip_checkURL(const char *URL) {
    xmlDocPtr doc;

    doc = xmlReadFile(URL, NULL, 0);
    if (doc == NULL)
        return(-1);
    xmlFreeDoc(doc);
    return(1);
}

/**
 * uriPathTest:
 * @filename: ignored
 * @result: ignored
 * @err: ignored
 *
 * Run a set of tests to check how Path and URI are handled before
 * being passed to the I/O layer
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
uriPathTest(const char *filename ATTRIBUTE_UNUSED,
             const char *result ATTRIBUTE_UNUSED,
             const char *err ATTRIBUTE_UNUSED,
             int options ATTRIBUTE_UNUSED) {
    int parsed;
    int failures = 0;

    /*
     * register the new I/O handlers
     */
    if (xmlRegisterInputCallbacks(uripMatch, uripOpen, uripRead, uripClose) < 0)
    {
        fprintf(stderr, "failed to register HTTP handler\n");
	return(-1);
    }

    for (urip_current = 0;urip_testURLs[urip_current] != NULL;urip_current++) {
        urip_success = 1;
        parsed = urip_checkURL(urip_testURLs[urip_current]);
	if (urip_success != 1) {
	    fprintf(stderr, "failed the URL passing test for %s",
	            urip_testURLs[urip_current]);
	    failures++;
	} else if (parsed != 1) {
	    fprintf(stderr, "failed the parsing test for %s",
	            urip_testURLs[urip_current]);
	    failures++;
	}
	nb_tests++;
    }

    xmlPopInputCallbacks();
    return(failures);
}

#ifdef LIBXML_SCHEMAS_ENABLED
/************************************************************************
 *									*
 *			Schemas tests					*
 *									*
 ************************************************************************/
static int
schemasOneTest(const char *sch,
               const char *filename,
               const char *result,
	       const char *err,
	       int options,
	       xmlSchemaPtr schemas) {
    xmlDocPtr doc;
    xmlSchemaValidCtxtPtr ctxt;
    int ret = 0;
    int validResult = 0;
    char *temp;
    FILE *schemasOutput;

    doc = xmlReadFile(filename, NULL, options);
    if (doc == NULL) {
        fprintf(stderr, "failed to parse instance %s for %s\n", filename, sch);
	return(-1);
    }

    temp = resultFilename(result, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    schemasOutput = fopen(temp, "wb");
    if (schemasOutput == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
	xmlFreeDoc(doc);
        free(temp);
	return(-1);
    }

    ctxt = xmlSchemaNewValidCtxt(schemas);
    xmlSchemaSetValidErrors(ctxt, testErrorHandler, testErrorHandler, ctxt);
    validResult = xmlSchemaValidateDoc(ctxt, doc);
    if (validResult == 0) {
	fprintf(schemasOutput, "%s validates\n", filename);
    } else if (validResult > 0) {
	fprintf(schemasOutput, "%s fails to validate\n", filename);
    } else {
	fprintf(schemasOutput, "%s validation generated an internal error\n",
	       filename);
    }
    fclose(schemasOutput);
    if (result) {
	if (compareFiles(temp, result)) {
	    fprintf(stderr, "Result for %s on %s failed\n", filename, sch);
	    ret = 1;
	}
    }
    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }

    if ((validResult != 0) && (err != NULL)) {
	if (compareFileMem(err, testErrors, testErrorsSize)) {
	    fprintf(stderr, "Error for %s on %s failed\n", filename, sch);
	    ret = 1;
	}
    }

    xmlSchemaFreeValidCtxt(ctxt);
    xmlFreeDoc(doc);
    return(ret);
}
/**
 * schemasTest:
 * @filename: the schemas file
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a file containing URI, compose them against a fixed base and
 * check for errors
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
schemasTest(const char *filename,
            const char *resul ATTRIBUTE_UNUSED,
            const char *errr ATTRIBUTE_UNUSED,
            int options) {
    const char *base = baseFilename(filename);
    const char *base2;
    const char *instance;
    xmlSchemaParserCtxtPtr ctxt;
    xmlSchemaPtr schemas;
    int res = 0, len, ret;
    char pattern[500];
    char prefix[500];
    char result[500];
    char err[500];
    glob_t globbuf;
    size_t i;
    char count = 0;

    /* first compile the schemas if possible */
    ctxt = xmlSchemaNewParserCtxt(filename);
    xmlSchemaSetParserErrors(ctxt, testErrorHandler, testErrorHandler, ctxt);
    schemas = xmlSchemaParse(ctxt);
    xmlSchemaFreeParserCtxt(ctxt);

    /*
     * most of the mess is about the output filenames generated by the Makefile
     */
    len = strlen(base);
    if ((len > 499) || (len < 5)) {
        xmlSchemaFree(schemas);
	return(-1);
    }
    len -= 4; /* remove trailing .xsd */
    if (base[len - 2] == '_') {
        len -= 2; /* remove subtest number */
    }
    if (base[len - 2] == '_') {
        len -= 2; /* remove subtest number */
    }
    memcpy(prefix, base, len);
    prefix[len] = 0;

    if (snprintf(pattern, 499, "./test/schemas/%s_?.xml", prefix) >= 499)
        pattern[499] = 0;

    if (base[len] == '_') {
        len += 2;
	memcpy(prefix, base, len);
	prefix[len] = 0;
    }

    globbuf.gl_offs = 0;
    glob(pattern, GLOB_DOOFFS, NULL, &globbuf);
    for (i = 0;i < globbuf.gl_pathc;i++) {
        testErrorsSize = 0;
	testErrors[0] = 0;
        instance = globbuf.gl_pathv[i];
	base2 = baseFilename(instance);
	len = strlen(base2);
	if ((len > 6) && (base2[len - 6] == '_')) {
	    count = base2[len - 5];
	    ret = snprintf(result, 499, "result/schemas/%s_%c",
		     prefix, count);
            if (ret >= 499)
	        result[499] = 0;
	    ret = snprintf(err, 499, "result/schemas/%s_%c.err",
		     prefix, count);
            if (ret >= 499)
	        err[499] = 0;
	} else {
	    fprintf(stderr, "don't know how to process %s\n", instance);
	    continue;
	}
	if (schemas == NULL) {
	} else {
	    nb_tests++;
	    ret = schemasOneTest(filename, instance, result, err,
	                         options, schemas);
	    if (ret != 0)
		res = ret;
	}
    }
    globfree(&globbuf);
    xmlSchemaFree(schemas);

    return(res);
}

/************************************************************************
 *									*
 *			Schemas tests					*
 *									*
 ************************************************************************/
static int
rngOneTest(const char *sch,
               const char *filename,
               const char *result,
	       const char *err,
	       int options,
	       xmlRelaxNGPtr schemas) {
    xmlDocPtr doc;
    xmlRelaxNGValidCtxtPtr ctxt;
    int ret = 0;
    char *temp;
    FILE *schemasOutput;

    doc = xmlReadFile(filename, NULL, options);
    if (doc == NULL) {
        fprintf(stderr, "failed to parse instance %s for %s\n", filename, sch);
	return(-1);
    }

    temp = resultFilename(result, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    schemasOutput = fopen(temp, "wb");
    if (schemasOutput == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
	xmlFreeDoc(doc);
        free(temp);
	return(-1);
    }

    ctxt = xmlRelaxNGNewValidCtxt(schemas);
    xmlRelaxNGSetValidErrors(ctxt, testErrorHandler, testErrorHandler, ctxt);
    ret = xmlRelaxNGValidateDoc(ctxt, doc);
    if (ret == 0) {
	testErrorHandler(NULL, "%s validates\n", filename);
    } else if (ret > 0) {
	testErrorHandler(NULL, "%s fails to validate\n", filename);
    } else {
	testErrorHandler(NULL, "%s validation generated an internal error\n",
	       filename);
    }
    fclose(schemasOutput);
    ret = 0;
    if (result) {
	if (compareFiles(temp, result)) {
	    fprintf(stderr, "Result for %s on %s failed\n", filename, sch);
	    ret = 1;
	}
    }
    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }

    if (err != NULL) {
	if (compareFileMem(err, testErrors, testErrorsSize)) {
	    fprintf(stderr, "Error for %s on %s failed\n", filename, sch);
	    ret = 1;
	    printf("%s", testErrors);
	}
    }


    xmlRelaxNGFreeValidCtxt(ctxt);
    xmlFreeDoc(doc);
    return(ret);
}
/**
 * rngTest:
 * @filename: the schemas file
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse an RNG schemas and then apply it to the related .xml
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
rngTest(const char *filename,
            const char *resul ATTRIBUTE_UNUSED,
            const char *errr ATTRIBUTE_UNUSED,
            int options) {
    const char *base = baseFilename(filename);
    const char *base2;
    const char *instance;
    xmlRelaxNGParserCtxtPtr ctxt;
    xmlRelaxNGPtr schemas;
    int res = 0, len, ret = 0;
    char pattern[500];
    char prefix[500];
    char result[500];
    char err[500];
    glob_t globbuf;
    size_t i;
    char count = 0;

    /* first compile the schemas if possible */
    ctxt = xmlRelaxNGNewParserCtxt(filename);
    xmlRelaxNGSetParserErrors(ctxt, testErrorHandler, testErrorHandler, ctxt);
    schemas = xmlRelaxNGParse(ctxt);
    xmlRelaxNGFreeParserCtxt(ctxt);

    /*
     * most of the mess is about the output filenames generated by the Makefile
     */
    len = strlen(base);
    if ((len > 499) || (len < 5)) {
        xmlRelaxNGFree(schemas);
	return(-1);
    }
    len -= 4; /* remove trailing .rng */
    memcpy(prefix, base, len);
    prefix[len] = 0;

    if (snprintf(pattern, 499, "./test/relaxng/%s_?.xml", prefix) >= 499)
        pattern[499] = 0;

    globbuf.gl_offs = 0;
    glob(pattern, GLOB_DOOFFS, NULL, &globbuf);
    for (i = 0;i < globbuf.gl_pathc;i++) {
        testErrorsSize = 0;
	testErrors[0] = 0;
        instance = globbuf.gl_pathv[i];
	base2 = baseFilename(instance);
	len = strlen(base2);
	if ((len > 6) && (base2[len - 6] == '_')) {
	    count = base2[len - 5];
	    res = snprintf(result, 499, "result/relaxng/%s_%c",
		     prefix, count);
            if (res >= 499)
	        result[499] = 0;
	    res = snprintf(err, 499, "result/relaxng/%s_%c.err",
		     prefix, count);
            if (res >= 499)
	        err[499] = 0;
	} else {
	    fprintf(stderr, "don't know how to process %s\n", instance);
	    continue;
	}
	if (schemas == NULL) {
	} else {
	    nb_tests++;
	    res = rngOneTest(filename, instance, result, err,
	                         options, schemas);
	    if (res != 0)
		ret = res;
	}
    }
    globfree(&globbuf);
    xmlRelaxNGFree(schemas);

    return(ret);
}

#ifdef LIBXML_READER_ENABLED
/**
 * rngStreamTest:
 * @filename: the schemas file
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a set of files with streaming, applying an RNG schemas
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
rngStreamTest(const char *filename,
            const char *resul ATTRIBUTE_UNUSED,
            const char *errr ATTRIBUTE_UNUSED,
            int options) {
    const char *base = baseFilename(filename);
    const char *base2;
    const char *instance;
    int res = 0, len, ret;
    char pattern[500];
    char prefix[500];
    char result[500];
    char err[500];
    glob_t globbuf;
    size_t i;
    char count = 0;
    xmlTextReaderPtr reader;
    int disable_err = 0;

    /*
     * most of the mess is about the output filenames generated by the Makefile
     */
    len = strlen(base);
    if ((len > 499) || (len < 5)) {
	fprintf(stderr, "len(base) == %d !\n", len);
	return(-1);
    }
    len -= 4; /* remove trailing .rng */
    memcpy(prefix, base, len);
    prefix[len] = 0;

    /*
     * strictly unifying the error messages is nearly impossible this
     * hack is also done in the Makefile
     */
    if ((!strcmp(prefix, "tutor10_1")) || (!strcmp(prefix, "tutor10_2")) ||
        (!strcmp(prefix, "tutor3_2")) || (!strcmp(prefix, "307377")) ||
        (!strcmp(prefix, "tutor8_2")))
	disable_err = 1;

    if (snprintf(pattern, 499, "./test/relaxng/%s_?.xml", prefix) >= 499)
        pattern[499] = 0;

    globbuf.gl_offs = 0;
    glob(pattern, GLOB_DOOFFS, NULL, &globbuf);
    for (i = 0;i < globbuf.gl_pathc;i++) {
        testErrorsSize = 0;
	testErrors[0] = 0;
        instance = globbuf.gl_pathv[i];
	base2 = baseFilename(instance);
	len = strlen(base2);
	if ((len > 6) && (base2[len - 6] == '_')) {
	    count = base2[len - 5];
	    ret = snprintf(result, 499, "result/relaxng/%s_%c",
		     prefix, count);
            if (ret >= 499)
	        result[499] = 0;
	    ret = snprintf(err, 499, "result/relaxng/%s_%c.err",
		     prefix, count);
            if (ret >= 499)
	        err[499] = 0;
	} else {
	    fprintf(stderr, "don't know how to process %s\n", instance);
	    continue;
	}
	reader = xmlReaderForFile(instance, NULL, options);
	if (reader == NULL) {
	    fprintf(stderr, "Failed to build reader for %s\n", instance);
	}
	if (disable_err == 1)
	    ret = streamProcessTest(instance, result, NULL, reader, filename,
	                            options);
	else
	    ret = streamProcessTest(instance, result, err, reader, filename,
	                            options);
	xmlFreeTextReader(reader);
	if (ret != 0) {
	    fprintf(stderr, "instance %s failed\n", instance);
	    res = ret;
	}
    }
    globfree(&globbuf);

    return(res);
}
#endif /* READER */

#endif

#ifdef LIBXML_PATTERN_ENABLED
#ifdef LIBXML_READER_ENABLED
/************************************************************************
 *									*
 *			Patterns tests					*
 *									*
 ************************************************************************/
static void patternNode(FILE *out, xmlTextReaderPtr reader,
                        const char *pattern, xmlPatternPtr patternc,
			xmlStreamCtxtPtr patstream) {
    xmlChar *path = NULL;
    int match = -1;
    int type, empty;

    type = xmlTextReaderNodeType(reader);
    empty = xmlTextReaderIsEmptyElement(reader);

    if (type == XML_READER_TYPE_ELEMENT) {
	/* do the check only on element start */
	match = xmlPatternMatch(patternc, xmlTextReaderCurrentNode(reader));

	if (match) {
	    path = xmlGetNodePath(xmlTextReaderCurrentNode(reader));
	    fprintf(out, "Node %s matches pattern %s\n", path, pattern);
	}
    }
    if (patstream != NULL) {
	int ret;

	if (type == XML_READER_TYPE_ELEMENT) {
	    ret = xmlStreamPush(patstream,
				xmlTextReaderConstLocalName(reader),
				xmlTextReaderConstNamespaceUri(reader));
	    if (ret < 0) {
		fprintf(out, "xmlStreamPush() failure\n");
		xmlFreeStreamCtxt(patstream);
		patstream = NULL;
	    } else if (ret != match) {
		if (path == NULL) {
		    path = xmlGetNodePath(
				   xmlTextReaderCurrentNode(reader));
		}
		fprintf(out,
			"xmlPatternMatch and xmlStreamPush disagree\n");
		fprintf(out,
			"  pattern %s node %s\n",
			pattern, path);
	    }


	}
	if ((type == XML_READER_TYPE_END_ELEMENT) ||
	    ((type == XML_READER_TYPE_ELEMENT) && (empty))) {
	    ret = xmlStreamPop(patstream);
	    if (ret < 0) {
		fprintf(out, "xmlStreamPop() failure\n");
		xmlFreeStreamCtxt(patstream);
		patstream = NULL;
	    }
	}
    }
    if (path != NULL)
	xmlFree(path);
}

/**
 * patternTest:
 * @filename: the schemas file
 * @result: the file with expected result
 * @err: the file with error messages
 *
 * Parse a set of files with streaming, applying an RNG schemas
 *
 * Returns 0 in case of success, an error code otherwise
 */
static int
patternTest(const char *filename,
            const char *resul ATTRIBUTE_UNUSED,
            const char *err ATTRIBUTE_UNUSED,
            int options) {
    xmlPatternPtr patternc = NULL;
    xmlStreamCtxtPtr patstream = NULL;
    FILE *o, *f;
    char str[1024];
    char xml[500];
    char result[500];
    int len, i;
    int ret = 0, res;
    char *temp;
    xmlTextReaderPtr reader;
    xmlDocPtr doc;

    len = strlen(filename);
    len -= 4;
    memcpy(xml, filename, len);
    xml[len] = 0;
    if (snprintf(result, 499, "result/pattern/%s", baseFilename(xml)) >= 499)
        result[499] = 0;
    memcpy(xml + len, ".xml", 5);

    if (!checkTestFile(xml) && !update_results) {
	fprintf(stderr, "Missing xml file %s\n", xml);
	return(-1);
    }
    if (!checkTestFile(result) && !update_results) {
	fprintf(stderr, "Missing result file %s\n", result);
	return(-1);
    }
    f = fopen(filename, "rb");
    if (f == NULL) {
        fprintf(stderr, "Failed to open %s\n", filename);
	return(-1);
    }
    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    o = fopen(temp, "wb");
    if (o == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
	fclose(f);
        free(temp);
	return(-1);
    }
    while (1) {
	/*
	 * read one line in string buffer.
	 */
	if (fgets (&str[0], sizeof (str) - 1, f) == NULL)
	   break;

	/*
	 * remove the ending spaces
	 */
	i = strlen(str);
	while ((i > 0) &&
	       ((str[i - 1] == '\n') || (str[i - 1] == '\r') ||
		(str[i - 1] == ' ') || (str[i - 1] == '\t'))) {
	    i--;
	    str[i] = 0;
	}
	doc = xmlReadFile(xml, NULL, options);
	if (doc == NULL) {
	    fprintf(stderr, "Failed to parse %s\n", xml);
	    ret = 1;
	} else {
	    xmlNodePtr root;
	    const xmlChar *namespaces[22];
	    int j;
	    xmlNsPtr ns;

	    root = xmlDocGetRootElement(doc);
	    for (ns = root->nsDef, j = 0;ns != NULL && j < 20;ns=ns->next) {
		namespaces[j++] = ns->href;
		namespaces[j++] = ns->prefix;
	    }
	    namespaces[j++] = NULL;
	    namespaces[j] = NULL;

	    patternc = xmlPatterncompile((const xmlChar *) str, doc->dict,
					 0, &namespaces[0]);
	    if (patternc == NULL) {
		testErrorHandler(NULL,
			"Pattern %s failed to compile\n", str);
		xmlFreeDoc(doc);
		ret = 1;
		continue;
	    }
	    patstream = xmlPatternGetStreamCtxt(patternc);
	    if (patstream != NULL) {
		ret = xmlStreamPush(patstream, NULL, NULL);
		if (ret < 0) {
		    fprintf(stderr, "xmlStreamPush() failure\n");
		    xmlFreeStreamCtxt(patstream);
		    patstream = NULL;
		}
	    }
	    nb_tests++;

	    reader = xmlReaderWalker(doc);
	    res = xmlTextReaderRead(reader);
	    while (res == 1) {
		patternNode(o, reader, str, patternc, patstream);
		res = xmlTextReaderRead(reader);
	    }
	    if (res != 0) {
		fprintf(o, "%s : failed to parse\n", filename);
	    }
	    xmlFreeTextReader(reader);
	    xmlFreeDoc(doc);
	    xmlFreeStreamCtxt(patstream);
	    patstream = NULL;
	    xmlFreePattern(patternc);

	}
    }

    fclose(f);
    fclose(o);

    ret = compareFiles(temp, result);
    if (ret) {
	fprintf(stderr, "Result for %s failed in %s\n", filename, result);
	ret = 1;
    }
    if (temp != NULL) {
        unlink(temp);
        free(temp);
    }
    return(ret);
}
#endif /* READER */
#endif /* PATTERN */
#ifdef LIBXML_C14N_ENABLED
/************************************************************************
 *									*
 *			Canonicalization tests				*
 *									*
 ************************************************************************/
static xmlXPathObjectPtr
load_xpath_expr (xmlDocPtr parent_doc, const char* filename) {
    xmlXPathObjectPtr xpath;
    xmlDocPtr doc;
    xmlChar *expr;
    xmlXPathContextPtr ctx;
    xmlNodePtr node;
    xmlNsPtr ns;

    /*
     * load XPath expr as a file
     */
    xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
    xmlSubstituteEntitiesDefault(1);

    doc = xmlReadFile(filename, NULL, XML_PARSE_DTDATTR | XML_PARSE_NOENT);
    if (doc == NULL) {
	fprintf(stderr, "Error: unable to parse file \"%s\"\n", filename);
	return(NULL);
    }

    /*
     * Check the document is of the right kind
     */
    if(xmlDocGetRootElement(doc) == NULL) {
        fprintf(stderr,"Error: empty document for file \"%s\"\n", filename);
	xmlFreeDoc(doc);
	return(NULL);
    }

    node = doc->children;
    while(node != NULL && !xmlStrEqual(node->name, (const xmlChar *)"XPath")) {
	node = node->next;
    }

    if(node == NULL) {
        fprintf(stderr,"Error: XPath element expected in the file  \"%s\"\n", filename);
	xmlFreeDoc(doc);
	return(NULL);
    }

    expr = xmlNodeGetContent(node);
    if(expr == NULL) {
        fprintf(stderr,"Error: XPath content element is NULL \"%s\"\n", filename);
	xmlFreeDoc(doc);
	return(NULL);
    }

    ctx = xmlXPathNewContext(parent_doc);
    if(ctx == NULL) {
        fprintf(stderr,"Error: unable to create new context\n");
        xmlFree(expr);
        xmlFreeDoc(doc);
        return(NULL);
    }

    /*
     * Register namespaces
     */
    ns = node->nsDef;
    while(ns != NULL) {
	if(xmlXPathRegisterNs(ctx, ns->prefix, ns->href) != 0) {
	    fprintf(stderr,"Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", ns->prefix, ns->href);
    xmlFree(expr);
	    xmlXPathFreeContext(ctx);
	    xmlFreeDoc(doc);
	    return(NULL);
	}
	ns = ns->next;
    }

    /*
     * Evaluate xpath
     */
    xpath = xmlXPathEvalExpression(expr, ctx);
    if(xpath == NULL) {
        fprintf(stderr,"Error: unable to evaluate xpath expression\n");
xmlFree(expr);
        xmlXPathFreeContext(ctx);
        xmlFreeDoc(doc);
        return(NULL);
    }

    /* print_xpath_nodes(xpath->nodesetval); */

    xmlFree(expr);
    xmlXPathFreeContext(ctx);
    xmlFreeDoc(doc);
    return(xpath);
}

/*
 * Macro used to grow the current buffer.
 */
#define xxx_growBufferReentrant() {						\
    buffer_size *= 2;							\
    buffer = (xmlChar **)						\
	xmlRealloc(buffer, buffer_size * sizeof(xmlChar*));	\
    if (buffer == NULL) {						\
	perror("realloc failed");					\
	return(NULL);							\
    }									\
}

static xmlChar **
parse_list(xmlChar *str) {
    xmlChar **buffer;
    xmlChar **out = NULL;
    int buffer_size = 0;
    int len;

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

    len = xmlStrlen(str);
    if((str[0] == '\'') && (str[len - 1] == '\'')) {
	str[len - 1] = '\0';
	str++;
    }
    /*
     * allocate an translation buffer.
     */
    buffer_size = 1000;
    buffer = (xmlChar **) xmlMalloc(buffer_size * sizeof(xmlChar*));
    if (buffer == NULL) {
	perror("malloc failed");
	return(NULL);
    }
    out = buffer;

    while(*str != '\0') {
	if (out - buffer > buffer_size - 10) {
	    int indx = out - buffer;

	    xxx_growBufferReentrant();
	    out = &buffer[indx];
	}
	(*out++) = str;
	while(*str != ',' && *str != '\0') ++str;
	if(*str == ',') *(str++) = '\0';
    }
    (*out) = NULL;
    return buffer;
}

static int
c14nRunTest(const char* xml_filename, int with_comments, int mode,
	    const char* xpath_filename, const char *ns_filename,
	    const char* result_file) {
    xmlDocPtr doc;
    xmlXPathObjectPtr xpath = NULL;
    xmlChar *result = NULL;
    int ret;
    xmlChar **inclusive_namespaces = NULL;
    const char *nslist = NULL;
    int nssize;


    /*
     * build an XML tree from a the file; we need to add default
     * attributes and resolve all character and entities references
     */
    xmlLoadExtDtdDefaultValue = XML_DETECT_IDS | XML_COMPLETE_ATTRS;
    xmlSubstituteEntitiesDefault(1);

    doc = xmlReadFile(xml_filename, NULL, XML_PARSE_DTDATTR | XML_PARSE_NOENT);
    if (doc == NULL) {
	fprintf(stderr, "Error: unable to parse file \"%s\"\n", xml_filename);
	return(-1);
    }

    /*
     * Check the document is of the right kind
     */
    if(xmlDocGetRootElement(doc) == NULL) {
        fprintf(stderr,"Error: empty document for file \"%s\"\n", xml_filename);
	xmlFreeDoc(doc);
	return(-1);
    }

    /*
     * load xpath file if specified
     */
    if(xpath_filename) {
	xpath = load_xpath_expr(doc, xpath_filename);
	if(xpath == NULL) {
	    fprintf(stderr,"Error: unable to evaluate xpath expression\n");
	    xmlFreeDoc(doc);
	    return(-1);
	}
    }

    if (ns_filename != NULL) {
        if (loadMem(ns_filename, &nslist, &nssize)) {
	    fprintf(stderr,"Error: unable to evaluate xpath expression\n");
	    if(xpath != NULL) xmlXPathFreeObject(xpath);
	    xmlFreeDoc(doc);
	    return(-1);
	}
        inclusive_namespaces = parse_list((xmlChar *) nslist);
    }

    /*
     * Canonical form
     */
    /* fprintf(stderr,"File \"%s\" loaded: start canonization\n", xml_filename); */
    ret = xmlC14NDocDumpMemory(doc,
	    (xpath) ? xpath->nodesetval : NULL,
	    mode, inclusive_namespaces,
	    with_comments, &result);
    if (ret >= 0) {
	if(result != NULL) {
	    if (compareFileMem(result_file, (const char *) result, ret)) {
		fprintf(stderr, "Result mismatch for %s\n", xml_filename);
		fprintf(stderr, "RESULT:\n%s\n", (const char*)result);
	        ret = -1;
	    }
	}
    } else {
	fprintf(stderr,"Error: failed to canonicalize XML file \"%s\" (ret=%d)\n", xml_filename, ret);
	ret = -1;
    }

    /*
     * Cleanup
     */
    if (result != NULL) xmlFree(result);
    if(xpath != NULL) xmlXPathFreeObject(xpath);
    if (inclusive_namespaces != NULL) xmlFree(inclusive_namespaces);
    if (nslist != NULL) free((char *) nslist);
    xmlFreeDoc(doc);

    return(ret);
}

static int
c14nCommonTest(const char *filename, int with_comments, int mode,
               const char *subdir) {
    char buf[500];
    char prefix[500];
    const char *base;
    int len;
    char *result = NULL;
    char *xpath = NULL;
    char *ns = NULL;
    int ret = 0;

    base = baseFilename(filename);
    len = strlen(base);
    len -= 4;
    memcpy(prefix, base, len);
    prefix[len] = 0;

    if (snprintf(buf, 499, "result/c14n/%s/%s", subdir, prefix) >= 499)
        buf[499] = 0;
    if (!checkTestFile(buf) && !update_results) {
        fprintf(stderr, "Missing result file %s", buf);
	return(-1);
    }
    result = strdup(buf);
    if (snprintf(buf, 499, "test/c14n/%s/%s.xpath", subdir, prefix) >= 499)
        buf[499] = 0;
    if (checkTestFile(buf)) {
	xpath = strdup(buf);
    }
    if (snprintf(buf, 499, "test/c14n/%s/%s.ns", subdir, prefix) >= 499)
        buf[499] = 0;
    if (checkTestFile(buf)) {
	ns = strdup(buf);
    }

    nb_tests++;
    if (c14nRunTest(filename, with_comments, mode,
                    xpath, ns, result) < 0)
        ret = 1;

    if (result != NULL) free(result);
    if (xpath != NULL) free(xpath);
    if (ns != NULL) free(ns);
    return(ret);
}

static int
c14nWithCommentTest(const char *filename,
                    const char *resul ATTRIBUTE_UNUSED,
		    const char *err ATTRIBUTE_UNUSED,
		    int options ATTRIBUTE_UNUSED) {
    return(c14nCommonTest(filename, 1, XML_C14N_1_0, "with-comments"));
}
static int
c14nWithoutCommentTest(const char *filename,
                    const char *resul ATTRIBUTE_UNUSED,
		    const char *err ATTRIBUTE_UNUSED,
		    int options ATTRIBUTE_UNUSED) {
    return(c14nCommonTest(filename, 0, XML_C14N_1_0, "without-comments"));
}
static int
c14nExcWithoutCommentTest(const char *filename,
                    const char *resul ATTRIBUTE_UNUSED,
		    const char *err ATTRIBUTE_UNUSED,
		    int options ATTRIBUTE_UNUSED) {
    return(c14nCommonTest(filename, 0, XML_C14N_EXCLUSIVE_1_0, "exc-without-comments"));
}
static int
c14n11WithoutCommentTest(const char *filename,
                    const char *resul ATTRIBUTE_UNUSED,
		    const char *err ATTRIBUTE_UNUSED,
		    int options ATTRIBUTE_UNUSED) {
    return(c14nCommonTest(filename, 0, XML_C14N_1_1, "1-1-without-comments"));
}
#endif
#if defined(LIBXML_THREAD_ENABLED) && defined(LIBXML_CATALOG_ENABLED)
/************************************************************************
 *									*
 *			Catalog and threads test			*
 *									*
 ************************************************************************/

/*
 * mostly a cut and paste from testThreads.c
 */
#define	MAX_ARGC	20

typedef struct {
    const char *filename;
    int okay;
} xmlThreadParams;

static const char *catalog = "test/threads/complex.xml";
static xmlThreadParams threadParams[] = {
    { "test/threads/abc.xml", 0 },
    { "test/threads/acb.xml", 0 },
    { "test/threads/bac.xml", 0 },
    { "test/threads/bca.xml", 0 },
    { "test/threads/cab.xml", 0 },
    { "test/threads/cba.xml", 0 },
    { "test/threads/invalid.xml", 0 }
};
static const unsigned int num_threads = sizeof(threadParams) /
                                        sizeof(threadParams[0]);

#ifndef xmlDoValidityCheckingDefaultValue
#error xmlDoValidityCheckingDefaultValue is not a macro
#endif
#ifndef xmlGenericErrorContext
#error xmlGenericErrorContext is not a macro
#endif

static void *
thread_specific_data(void *private_data)
{
    xmlDocPtr myDoc;
    xmlThreadParams *params = (xmlThreadParams *) private_data;
    const char *filename = params->filename;
    int okay = 1;

    if (!strcmp(filename, "test/threads/invalid.xml")) {
        xmlDoValidityCheckingDefaultValue = 0;
        xmlGenericErrorContext = stdout;
    } else {
        xmlDoValidityCheckingDefaultValue = 1;
        xmlGenericErrorContext = stderr;
    }
#ifdef LIBXML_SAX1_ENABLED
    myDoc = xmlParseFile(filename);
#else
    myDoc = xmlReadFile(filename, NULL, XML_WITH_CATALOG);
#endif
    if (myDoc) {
        xmlFreeDoc(myDoc);
    } else {
        printf("parse failed\n");
        okay = 0;
    }
    if (!strcmp(filename, "test/threads/invalid.xml")) {
        if (xmlDoValidityCheckingDefaultValue != 0) {
            printf("ValidityCheckingDefaultValue override failed\n");
            okay = 0;
        }
        if (xmlGenericErrorContext != stdout) {
            printf("xmlGenericErrorContext override failed\n");
            okay = 0;
        }
    } else {
        if (xmlDoValidityCheckingDefaultValue != 1) {
            printf("ValidityCheckingDefaultValue override failed\n");
            okay = 0;
        }
        if (xmlGenericErrorContext != stderr) {
            printf("xmlGenericErrorContext override failed\n");
            okay = 0;
        }
    }
    params->okay = okay;
    return(NULL);
}

#if defined(_WIN32) && !defined(__CYGWIN__)
#include <windows.h>
#include <string.h>

#define TEST_REPEAT_COUNT 500

static HANDLE tid[MAX_ARGC];

static DWORD WINAPI
win32_thread_specific_data(void *private_data)
{
    thread_specific_data(private_data);
    return(0);
}

static int
testThread(void)
{
    unsigned int i, repeat;
    BOOL ret;
    int res = 0;

    xmlInitParser();
    for (repeat = 0; repeat < TEST_REPEAT_COUNT; repeat++) {
        xmlLoadCatalog(catalog);
        nb_tests++;

        for (i = 0; i < num_threads; i++) {
            tid[i] = (HANDLE) - 1;
        }

        for (i = 0; i < num_threads; i++) {
            DWORD useless;

            tid[i] = CreateThread(NULL, 0,
                                  win32_thread_specific_data,
				  (void *) &threadParams[i], 0,
                                  &useless);
            if (tid[i] == NULL) {
                fprintf(stderr, "CreateThread failed\n");
                return(1);
            }
        }

        if (WaitForMultipleObjects(num_threads, tid, TRUE, INFINITE) ==
            WAIT_FAILED) {
            fprintf(stderr, "WaitForMultipleObjects failed\n");
	    return(1);
	}

        for (i = 0; i < num_threads; i++) {
            DWORD exitCode;
            ret = GetExitCodeThread(tid[i], &exitCode);
            if (ret == 0) {
                fprintf(stderr, "GetExitCodeThread failed\n");
                return(1);
            }
            CloseHandle(tid[i]);
        }

        xmlCatalogCleanup();
        for (i = 0; i < num_threads; i++) {
            if (threadParams[i].okay == 0) {
                fprintf(stderr, "Thread %d handling %s failed\n",
		        i, threadParams[i].filename);
	        res = 1;
	    }
        }
    }

    return (res);
}

#elif defined __BEOS__
#include <OS.h>

static thread_id tid[MAX_ARGC];

static int
testThread(void)
{
    unsigned int i, repeat;
    status_t ret;
    int res = 0;

    xmlInitParser();
    for (repeat = 0; repeat < 500; repeat++) {
        xmlLoadCatalog(catalog);
        for (i = 0; i < num_threads; i++) {
            tid[i] = (thread_id) - 1;
        }
        for (i = 0; i < num_threads; i++) {
            tid[i] =
                spawn_thread(thread_specific_data, "xmlTestThread",
                             B_NORMAL_PRIORITY, (void *) &threadParams[i]);
            if (tid[i] < B_OK) {
                fprintf(stderr, "beos_thread_create failed\n");
                return (1);
            }
            printf("beos_thread_create %d -> %d\n", i, tid[i]);
        }
        for (i = 0; i < num_threads; i++) {
            void *result;
            ret = wait_for_thread(tid[i], &result);
            printf("beos_thread_wait %d -> %d\n", i, ret);
            if (ret != B_OK) {
                fprintf(stderr, "beos_thread_wait failed\n");
                return (1);
            }
        }

        xmlCatalogCleanup();
        ret = B_OK;
        for (i = 0; i < num_threads; i++)
            if (threadParams[i].okay == 0) {
                printf("Thread %d handling %s failed\n", i,
                       threadParams[i].filename);
                ret = B_ERROR;
            }
    }
    if (ret != B_OK)
        return(1);
    return (0);
}

#elif defined HAVE_PTHREAD_H
#include <pthread.h>

static pthread_t tid[MAX_ARGC];

static int
testThread(void)
{
    unsigned int i, repeat;
    int ret;
    int res = 0;

    xmlInitParser();

    for (repeat = 0; repeat < 500; repeat++) {
        xmlLoadCatalog(catalog);
        nb_tests++;

        for (i = 0; i < num_threads; i++) {
            tid[i] = (pthread_t) - 1;
        }

        for (i = 0; i < num_threads; i++) {
            ret = pthread_create(&tid[i], 0, thread_specific_data,
                                 (void *) &threadParams[i]);
            if (ret != 0) {
                fprintf(stderr, "pthread_create failed\n");
                return (1);
            }
        }
        for (i = 0; i < num_threads; i++) {
            void *result;
            ret = pthread_join(tid[i], &result);
            if (ret != 0) {
                fprintf(stderr, "pthread_join failed\n");
                return (1);
            }
        }

        xmlCatalogCleanup();
        for (i = 0; i < num_threads; i++)
            if (threadParams[i].okay == 0) {
                fprintf(stderr, "Thread %d handling %s failed\n",
                        i, threadParams[i].filename);
                res = 1;
            }
    }
    return (res);
}

#else
static int
testThread(void)
{
    fprintf(stderr,
            "Specific platform thread support not detected\n");
    return (-1);
}
#endif
static int
threadsTest(const char *filename ATTRIBUTE_UNUSED,
	    const char *resul ATTRIBUTE_UNUSED,
	    const char *err ATTRIBUTE_UNUSED,
	    int options ATTRIBUTE_UNUSED) {
    return(testThread());
}
#endif
/************************************************************************
 *									*
 *			Tests Descriptions				*
 *									*
 ************************************************************************/

static
testDesc testDescriptions[] = {
    { "XML regression tests" ,
      oldParseTest, "./test/*", "result/", "", NULL,
      0 },
    { "XML regression tests on memory" ,
      memParseTest, "./test/*", "result/", "", NULL,
      0 },
    { "XML entity subst regression tests" ,
      noentParseTest, "./test/*", "result/noent/", "", NULL,
      XML_PARSE_NOENT },
    { "XML Namespaces regression tests",
      errParseTest, "./test/namespaces/*", "result/namespaces/", "", ".err",
      0 },
    { "Error cases regression tests",
      errParseTest, "./test/errors/*.xml", "result/errors/", "", ".err",
      0 },
    { "Error cases regression tests (old 1.0)",
      errParseTest, "./test/errors10/*.xml", "result/errors10/", "", ".err",
      XML_PARSE_OLD10 },
#ifdef LIBXML_READER_ENABLED
    { "Error cases stream regression tests",
      streamParseTest, "./test/errors/*.xml", "result/errors/", NULL, ".str",
      0 },
    { "Reader regression tests",
      streamParseTest, "./test/*", "result/", ".rdr", NULL,
      0 },
    { "Reader entities substitution regression tests",
      streamParseTest, "./test/*", "result/", ".rde", NULL,
      XML_PARSE_NOENT },
    { "Reader on memory regression tests",
      streamMemParseTest, "./test/*", "result/", ".rdr", NULL,
      0 },
    { "Walker regression tests",
      walkerParseTest, "./test/*", "result/", ".rdr", NULL,
      0 },
#endif
#ifdef LIBXML_SAX1_ENABLED
    { "SAX1 callbacks regression tests" ,
      saxParseTest, "./test/*", "result/", ".sax", NULL,
      XML_PARSE_SAX1 },
#endif
    { "SAX2 callbacks regression tests" ,
      saxParseTest, "./test/*", "result/", ".sax2", NULL,
      0 },
    { "SAX2 callbacks regression tests with entity substitution" ,
      saxParseTest, "./test/*", "result/noent/", ".sax2", NULL,
      XML_PARSE_NOENT },
#ifdef LIBXML_PUSH_ENABLED
    { "XML push regression tests" ,
      pushParseTest, "./test/*", "result/", "", NULL,
      0 },
#endif
#ifdef LIBXML_HTML_ENABLED
    { "HTML regression tests" ,
      errParseTest, "./test/HTML/*", "result/HTML/", "", ".err",
      XML_PARSE_HTML },
#ifdef LIBXML_PUSH_ENABLED
    { "Push HTML regression tests" ,
      pushParseTest, "./test/HTML/*", "result/HTML/", "", ".err",
      XML_PARSE_HTML },
#endif
    { "HTML SAX regression tests" ,
      saxParseTest, "./test/HTML/*", "result/HTML/", ".sax", NULL,
      XML_PARSE_HTML },
#endif
#ifdef LIBXML_VALID_ENABLED
    { "Valid documents regression tests" ,
      errParseTest, "./test/VCM/*", NULL, NULL, NULL,
      XML_PARSE_DTDVALID },
    { "Validity checking regression tests" ,
      errParseTest, "./test/VC/*", "result/VC/", NULL, "",
      XML_PARSE_DTDVALID },
#ifdef LIBXML_READER_ENABLED
    { "Streaming validity checking regression tests" ,
      streamParseTest, "./test/valid/*.xml", "result/valid/", NULL, ".err.rdr",
      XML_PARSE_DTDVALID },
    { "Streaming validity error checking regression tests" ,
      streamParseTest, "./test/VC/*", "result/VC/", NULL, ".rdr",
      XML_PARSE_DTDVALID },
#endif
    { "General documents valid regression tests" ,
      errParseTest, "./test/valid/*", "result/valid/", "", ".err",
      XML_PARSE_DTDVALID },
#endif
#ifdef LIBXML_XINCLUDE_ENABLED
    { "XInclude regression tests" ,
      errParseTest, "./test/XInclude/docs/*", "result/XInclude/", "", NULL,
      /* Ignore errors at this point ".err", */
      XML_PARSE_XINCLUDE },
#ifdef LIBXML_READER_ENABLED
    { "XInclude xmlReader regression tests",
      streamParseTest, "./test/XInclude/docs/*", "result/XInclude/", ".rdr",
      /* Ignore errors at this point ".err", */
      NULL, XML_PARSE_XINCLUDE },
#endif
    { "XInclude regression tests stripping include nodes" ,
      errParseTest, "./test/XInclude/docs/*", "result/XInclude/", "", NULL,
      /* Ignore errors at this point ".err", */
      XML_PARSE_XINCLUDE | XML_PARSE_NOXINCNODE },
#ifdef LIBXML_READER_ENABLED
    { "XInclude xmlReader regression tests stripping include nodes",
      streamParseTest, "./test/XInclude/docs/*", "result/XInclude/", ".rdr",
      /* Ignore errors at this point ".err", */
      NULL, XML_PARSE_XINCLUDE | XML_PARSE_NOXINCNODE },
#endif
#endif
#ifdef LIBXML_XPATH_ENABLED
#ifdef LIBXML_DEBUG_ENABLED
    { "XPath expressions regression tests" ,
      xpathExprTest, "./test/XPath/expr/*", "result/XPath/expr/", "", NULL,
      0 },
    { "XPath document queries regression tests" ,
      xpathDocTest, "./test/XPath/docs/*", NULL, NULL, NULL,
      0 },
#ifdef LIBXML_XPTR_ENABLED
    { "XPointer document queries regression tests" ,
      xptrDocTest, "./test/XPath/docs/*", NULL, NULL, NULL,
      0 },
#endif
    { "xml:id regression tests" ,
      xmlidDocTest, "./test/xmlid/*", "result/xmlid/", "", ".err",
      0 },
#endif
#endif
    { "URI parsing tests" ,
      uriParseTest, "./test/URI/*.uri", "result/URI/", "", NULL,
      0 },
    { "URI base composition tests" ,
      uriBaseTest, "./test/URI/*.data", "result/URI/", "", NULL,
      0 },
    { "Path URI conversion tests" ,
      uriPathTest, NULL, NULL, NULL, NULL,
      0 },
#ifdef LIBXML_SCHEMAS_ENABLED
    { "Schemas regression tests" ,
      schemasTest, "./test/schemas/*_*.xsd", NULL, NULL, NULL,
      0 },
    { "Relax-NG regression tests" ,
      rngTest, "./test/relaxng/*.rng", NULL, NULL, NULL,
      XML_PARSE_DTDATTR | XML_PARSE_NOENT },
#ifdef LIBXML_READER_ENABLED
    { "Relax-NG streaming regression tests" ,
      rngStreamTest, "./test/relaxng/*.rng", NULL, NULL, NULL,
      XML_PARSE_DTDATTR | XML_PARSE_NOENT },
#endif
#endif
#ifdef LIBXML_PATTERN_ENABLED
#ifdef LIBXML_READER_ENABLED
    { "Pattern regression tests" ,
      patternTest, "./test/pattern/*.pat", "result/pattern/", NULL, NULL,
      0 },
#endif
#endif
#ifdef LIBXML_C14N_ENABLED
    { "C14N with comments regression tests" ,
      c14nWithCommentTest, "./test/c14n/with-comments/*.xml", NULL, NULL, NULL,
      0 },
    { "C14N without comments regression tests" ,
      c14nWithoutCommentTest, "./test/c14n/without-comments/*.xml", NULL, NULL, NULL,
      0 },
    { "C14N exclusive without comments regression tests" ,
      c14nExcWithoutCommentTest, "./test/c14n/exc-without-comments/*.xml", NULL, NULL, NULL,
      0 },
    { "C14N 1.1 without comments regression tests" ,
      c14n11WithoutCommentTest, "./test/c14n/1-1-without-comments/*.xml", NULL, NULL, NULL,
      0 },
#endif
#if defined(LIBXML_THREAD_ENABLED) && defined(LIBXML_CATALOG_ENABLED)
    { "Catalog and Threads regression tests" ,
      threadsTest, NULL, NULL, NULL, NULL,
      0 },
#endif
    {NULL, NULL, NULL, NULL, NULL, NULL, 0}
};

/************************************************************************
 *									*
 *		The main code driving the tests				*
 *									*
 ************************************************************************/

static int
launchTests(testDescPtr tst) {
    int res = 0, err = 0;
    size_t i;
    char *result;
    char *error;
    int mem;
    xmlCharEncodingHandlerPtr ebcdicHandler, eucJpHandler;

    ebcdicHandler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_EBCDIC);
    eucJpHandler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_EUC_JP);

    if (tst == NULL) return(-1);
    if (tst->in != NULL) {
	glob_t globbuf;

	globbuf.gl_offs = 0;
	glob(tst->in, GLOB_DOOFFS, NULL, &globbuf);
	for (i = 0;i < globbuf.gl_pathc;i++) {
	    if (!checkTestFile(globbuf.gl_pathv[i]))
	        continue;
            if (((ebcdicHandler == NULL) &&
                 (strstr(globbuf.gl_pathv[i], "ebcdic") != NULL)) ||
                ((eucJpHandler == NULL) &&
                 (strstr(globbuf.gl_pathv[i], "icu_parse_test") != NULL)))
                continue;
	    if (tst->suffix != NULL) {
		result = resultFilename(globbuf.gl_pathv[i], tst->out,
					tst->suffix);
		if (result == NULL) {
		    fprintf(stderr, "Out of memory !\n");
		    fatalError();
		}
	    } else {
	        result = NULL;
	    }
	    if (tst->err != NULL) {
		error = resultFilename(globbuf.gl_pathv[i], tst->out,
		                        tst->err);
		if (error == NULL) {
		    fprintf(stderr, "Out of memory !\n");
		    fatalError();
		}
	    } else {
	        error = NULL;
	    }
	    if ((result) &&(!checkTestFile(result)) && !update_results) {
	        fprintf(stderr, "Missing result file %s\n", result);
	    } else if ((error) &&(!checkTestFile(error)) && !update_results) {
	        fprintf(stderr, "Missing error file %s\n", error);
	    } else {
		mem = xmlMemUsed();
		extraMemoryFromResolver = 0;
		testErrorsSize = 0;
		testErrors[0] = 0;
		res = tst->func(globbuf.gl_pathv[i], result, error,
		                tst->options | XML_PARSE_COMPACT);
		xmlResetLastError();
		if (res != 0) {
		    fprintf(stderr, "File %s generated an error\n",
		            globbuf.gl_pathv[i]);
		    nb_errors++;
		    err++;
		}
		else if (xmlMemUsed() != mem) {
		    if ((xmlMemUsed() != mem) &&
		        (extraMemoryFromResolver == 0)) {
			fprintf(stderr, "File %s leaked %d bytes\n",
				globbuf.gl_pathv[i], xmlMemUsed() - mem);
			nb_leaks++;
			err++;
		    }
		}
		testErrorsSize = 0;
	    }
	    if (result)
		free(result);
	    if (error)
		free(error);
	}
	globfree(&globbuf);
    } else {
        testErrorsSize = 0;
	testErrors[0] = 0;
	extraMemoryFromResolver = 0;
        res = tst->func(NULL, NULL, NULL, tst->options);
	if (res != 0) {
	    nb_errors++;
	    err++;
	}
    }

    xmlCharEncCloseFunc(ebcdicHandler);
    xmlCharEncCloseFunc(eucJpHandler);

    return(err);
}

static int verbose = 0;
static int tests_quiet = 0;

static int
runtest(int i) {
    int ret = 0, res;
    int old_errors, old_tests, old_leaks;

    old_errors = nb_errors;
    old_tests = nb_tests;
    old_leaks = nb_leaks;
    if ((tests_quiet == 0) && (testDescriptions[i].desc != NULL))
	printf("## %s\n", testDescriptions[i].desc);
    res = launchTests(&testDescriptions[i]);
    if (res != 0)
	ret++;
    if (verbose) {
	if ((nb_errors == old_errors) && (nb_leaks == old_leaks))
	    printf("Ran %d tests, no errors\n", nb_tests - old_tests);
	else
	    printf("Ran %d tests, %d errors, %d leaks\n",
		   nb_tests - old_tests,
		   nb_errors - old_errors,
		   nb_leaks - old_leaks);
    }
    return(ret);
}

int
main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
    int i, a, ret = 0;
    int subset = 0;

#if defined(_WIN32) && !defined(__CYGWIN__)
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
#endif

#if defined(_MSC_VER) && _MSC_VER >= 1400 && _MSC_VER < 1900
    _set_output_format(_TWO_DIGIT_EXPONENT);
#endif

    initializeLibxml2();

    for (a = 1; a < argc;a++) {
        if (!strcmp(argv[a], "-v"))
	    verbose = 1;
        else if (!strcmp(argv[a], "-u"))
	    update_results = 1;
        else if (!strcmp(argv[a], "-quiet"))
	    tests_quiet = 1;
	else {
	    for (i = 0; testDescriptions[i].func != NULL; i++) {
	        if (strstr(testDescriptions[i].desc, argv[a])) {
		    ret += runtest(i);
		    subset++;
		}
	    }
	}
    }
    if (subset == 0) {
	for (i = 0; testDescriptions[i].func != NULL; i++) {
	    ret += runtest(i);
	}
    }
    if ((nb_errors == 0) && (nb_leaks == 0)) {
        ret = 0;
	printf("Total %d tests, no errors\n",
	       nb_tests);
    } else {
        ret = 1;
	printf("Total %d tests, %d errors, %d leaks\n",
	       nb_tests, nb_errors, nb_leaks);
    }
    xmlCleanupParser();
    xmlMemoryDump();

    return(ret);
}

#else /* ! LIBXML_OUTPUT_ENABLED */
int
main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
    fprintf(stderr, "runtest requires output to be enabled in libxml2\n");
    return(1);
}
#endif
