/*
 * 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.
 *
 * Author: Daniel Veillard
 */

#define XML_DEPRECATED
#define XML_DEPRECATED_MEMBER

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

#include <fcntl.h>
#include <sys/stat.h>

#ifdef _WIN32
  #include <io.h>
#else
  #include <unistd.h>
#endif

#ifdef LIBXML_ZLIB_ENABLED
#include <zlib.h>
#endif

#include <libxml/parser.h>
#include <libxml/parserInternals.h>
#include <libxml/tree.h>
#include <libxml/uri.h>
#include <libxml/encoding.h>
#include <libxml/xmlsave.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_RELAXNG_ENABLED
#include <libxml/relaxng.h>
#endif

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

#ifdef LIBXML_SCHEMATRON_ENABLED
#include <libxml/schematron.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>
#endif

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

#ifdef LIBXML_CATALOG_ENABLED
#include <libxml/catalog.h>
#endif

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

/*
 * 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 char* temp_directory = NULL;
static int checkTestFile(const char *filename);

#if defined(_WIN32)

#include <windows.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 **) xmlMalloc(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] = xmlMemStrdup(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 = xmlRealloc(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] = xmlMemStrdup(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)
             xmlFree(pglob->gl_pathv[i]);
    }
    xmlFree(pglob->gl_pathv);
}

#elif HAVE_DECL_GLOB

#include <glob.h>

#else /* _WIN32, HAVE_DECL_GLOB */

#define GLOB_DOOFFS 0

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;

static int
glob(const char *pattern ATTRIBUTE_UNUSED, int flags ATTRIBUTE_UNUSED,
     int errfunc(const char *epath, int eerrno) ATTRIBUTE_UNUSED,
     glob_t *pglob) {
    pglob->gl_pathc = 0;
    pglob->gl_pathv = NULL;

    return(0);
}

static void
globfree(glob_t *pglob ATTRIBUTE_UNUSED) {
}

#endif /* _WIN32, HAVE_DECL_GLOB */

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

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

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

/*
 * 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
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
testStructuredErrorHandler(void *ctx ATTRIBUTE_UNUSED, const xmlError *err) {
    xmlFormatError(err, testErrorHandler, NULL);
}

static void
initializeLibxml2(void) {
    /*
     * This verifies that xmlInitParser doesn't allocate memory with
     * xmlMalloc
     */
    xmlFree = NULL;
    xmlMalloc = NULL;
    xmlRealloc = NULL;
    xmlMemStrdup = NULL;
    xmlInitParser();
    xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
#ifdef LIBXML_CATALOG_ENABLED
    /*
     * Disable system catalog which could cause lazy memory allocations
     * resulting in false positive memory leaks.
     */
#ifdef _WIN32
    putenv("XML_CATALOG_FILES=");
#else
    setenv("XML_CATALOG_FILES", "", 1);
#endif
    xmlInitializeCatalog();
#endif
#ifdef LIBXML_RELAXNG_ENABLED
    xmlRelaxNGInitTypes();
#endif
#ifdef LIBXML_SCHEMAS_ENABLED
    xmlSchemaInitTypes();
#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);

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

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

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

#if defined(_WIN32)
    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, total;
    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);
        }
        total = 0;
        do {
            res1 = read(fd1, bytes1, 4096);
            if (res1 <= 0)
                break;
            total += res1;
            res2 = write(fd2, bytes1, res1);
            if (res2 <= 0 || res2 != res1)
                break;
        } while (1);
        close(fd2);
        close(fd1);
        if (total == 0)
            unlink(r2);
        return(res1 != 0);
    }

    fd1 = open(r1, RD_FLAGS);
    if (fd1 < 0)
        return(-1);
    fd2 = open(r2, RD_FLAGS);
    while (1) {
        res1 = read(fd1, bytes1, 4096);
        res2 = fd2 >= 0 ? read(fd2, bytes2, 4096) : 0;
	if ((res1 != res2) || (res1 < 0)) {
	    close(fd1);
            if (fd2 >= 0)
                close(fd2);
	    return(1);
	}
	if (res1 == 0)
	    break;
	if (memcmp(bytes1, bytes2, res1) != 0) {
	    close(fd1);
            if (fd2 >= 0)
                close(fd2);
	    return(1);
	}
    }
    close(fd1);
    if (fd2 >= 0)
        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) {
        if (size == 0) {
            unlink(filename);
            return(0);
        }
        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) {
        if (size == 0)
            return(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 = xmlMalloc(info.st_size + 1);
    if (base == NULL)
	return(-1);
    fd = open(filename, RD_FLAGS);
    if (fd  < 0) {
        xmlFree(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) {
        xmlFree(base);
	return(-1);
    }
#endif
    base[siz] = 0;
    *mem = base;
    *size = siz;
    return(0);
}

static int unloadMem(const char *mem) {
    xmlFree((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 */
};

typedef struct {
    const char *filename;
    xmlHashTablePtr generalEntities;
    xmlHashTablePtr parameterEntities;
} debugContext;

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

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

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

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

/**
 * Does this document has an internal subset
 *
 * @param ctxt  An XML parser context
 */
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);
}

/**
 * Does this document has an external subset
 *
 * @param ctxt  An XML parser context
 */
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);
}

/**
 * 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).
 *
 * @param ctxt  An XML parser context
 * @param publicId  The public ID of the entity
 * @param systemId  The system ID of the entity
 * @returns the xmlParserInput 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);
}

/**
 * Get an entity by name
 *
 * @param ctxt  An XML parser context
 * @param name  The entity name
 * @returns the xmlParserInput if inlined or NULL for DOM behaviour.
 */
static xmlEntityPtr
getEntityDebug(void *ctx, const xmlChar *name)
{
    debugContext *ctxt = ctx;

    callbacks++;
    if (quiet)
	return(NULL);
    fprintf(SAXdebug, "SAX.getEntity(%s)\n", name);

    return(xmlHashLookup(ctxt->generalEntities, name));
}

/**
 * Get a parameter entity by name
 *
 * @param ctxt  An XML parser context
 * @param name  The entity name
 * @returns the xmlParserInput
 */
static xmlEntityPtr
getParameterEntityDebug(void *ctx, const xmlChar *name)
{
    debugContext *ctxt = ctx;

    callbacks++;
    if (quiet)
	return(NULL);
    fprintf(SAXdebug, "SAX.getParameterEntity(%s)\n", name);

    return(xmlHashLookup(ctxt->parameterEntities, name));
}


/**
 * An entity definition has been parsed
 *
 * @param ctxt  An XML parser context
 * @param name  the entity name
 * @param type  the entity type
 * @param publicId  The public ID of the entity
 * @param systemId  The system ID of the entity
 * @param content  the entity value (without processing).
 */
static void
entityDeclDebug(void *ctx, const xmlChar *name, int type,
          const xmlChar *publicId, const xmlChar *systemId, xmlChar *content)
{
    debugContext *ctxt = ctx;
    xmlEntityPtr ent;
    const xmlChar *nullstr = BAD_CAST "(null)";

    ent = xmlNewEntity(NULL, name, type, publicId, systemId, content);
    if (systemId != NULL)
        ent->URI = xmlBuildURI(systemId, (const xmlChar *) ctxt->filename);

    if ((type == XML_INTERNAL_PARAMETER_ENTITY) ||
        (type == XML_EXTERNAL_PARAMETER_ENTITY))
        xmlHashAddEntry(ctxt->parameterEntities, name, ent);
    else
        xmlHashAddEntry(ctxt->generalEntities, name, ent);

    /* 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);
}

/**
 * An attribute definition has been parsed
 *
 * @param ctxt  An XML parser context
 * @param name  the attribute name
 * @param type  the attribute type
 */
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);
}

/**
 * An element definition has been parsed
 *
 * @param ctxt  An XML parser context
 * @param name  the element name
 * @param type  the element type
 * @param content  the element value (without processing).
 */
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);
}

/**
 * What to do when a notation declaration has been parsed.
 *
 * @param ctxt  An XML parser context
 * @param name  The name of the notation
 * @param publicId  The public ID of the entity
 * @param systemId  The system ID of the entity
 */
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);
}

/**
 * What to do when an unparsed entity declaration is parsed
 *
 * @param ctxt  An XML parser context
 * @param name  The name of the entity
 * @param publicId  The public ID of the entity
 * @param systemId  The system ID of the entity
 * @param notationName  the name of the notation
 */
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);
}

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

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

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

/**
 * called when an opening tag has been processed.
 *
 * @param ctxt  An XML parser context
 * @param name  The element name
 */
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");
}

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

/**
 * receiving some chars from the parser.
 * Question: how much at a time ???
 *
 * @param ctxt  An XML parser context
 * @param ch  a xmlChar string
 * @param len  the number of xmlChar
 */
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] = (char) ch[i];
    output[i] = 0;

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

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

/**
 * receiving some ignorable whitespaces from the parser.
 * Question: how much at a time ???
 *
 * @param ctxt  An XML parser context
 * @param ch  a xmlChar string
 * @param start  the first char in the string
 * @param len  the number of xmlChar
 */
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] = (char) ch[i];
    output[i] = 0;
    fprintf(SAXdebug, "SAX.ignorableWhitespace(%s, %d)\n", output, len);
}

/**
 * A processing instruction has been parsed.
 *
 * @param ctxt  An XML parser context
 * @param target  the target name
 * @param data  the PI data's
 * @param len  the number of xmlChar
 */
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);
}

/**
 * called when a pcdata block has been parsed
 *
 * @param ctx  the user data (XML parser context)
 * @param value  The pcdata content
 * @param len  the block length
 */
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);
}

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

/**
 * Display and format a warning messages, gives file, line, position and
 * extra parameters.
 *
 * @param ctxt  An XML parser context
 * @param msg  the message to display/transmit
 * @param ...  extra parameters for the message display
 */
static void
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);
}

/**
 * Display and format a error messages, gives file, line, position and
 * extra parameters.
 *
 * @param ctxt  An XML parser context
 * @param msg  the message to display/transmit
 * @param ...  extra parameters for the message display
 */
static void
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);
}

/**
 * Display and format a fatalError messages, gives file, line, position and
 * extra parameters.
 *
 * @param ctxt  An XML parser context
 * @param msg  the message to display/transmit
 * @param ...  extra parameters for the message display
 */
static void
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
 */
/**
 * called when an opening tag has been processed.
 *
 * @param ctxt  An XML parser context
 * @param name  The element name
 */
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");
}

/**
 * called when the end of an element has been detected.
 *
 * @param ctxt  An XML parser context
 * @param name  The element name
 */
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
/**
 * called when an opening tag has been processed.
 *
 * @param ctxt  An XML parser context
 * @param name  The element name
 */
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");
}

/**
 * receiving some chars from the parser.
 * Question: how much at a time ???
 *
 * @param ctxt  An XML parser context
 * @param ch  a xmlChar string
 * @param len  the number of xmlChar
 */
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);
}

/**
 * receiving some cdata chars from the parser.
 * Question: how much at a time ???
 *
 * @param ctxt  An XML parser context
 * @param ch  a xmlChar string
 * @param len  the number of xmlChar
 */
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 */

static void
hashFreeEntity(void *payload, const xmlChar *name ATTRIBUTE_UNUSED) {
    xmlEntityPtr ent = payload;

    xmlFreeEntity(ent);
}

/**
 * Parse a file using the SAX API and check for errors.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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, temp_directory, ".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);
	xmlFree(temp);
	return(-1);
    }

#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
        htmlParserCtxtPtr ctxt;

        ctxt = htmlNewSAXParserCtxt(emptySAXHandler, NULL);
        htmlCtxtReadFile(ctxt, filename, NULL, options);
        htmlFreeParserCtxt(ctxt);
	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_ERR_UNDECLARED_ENTITY) {
        fprintf(SAXdebug, "xmlParseDocument 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) {
        htmlParserCtxtPtr ctxt;

        ctxt = htmlNewSAXParserCtxt(debugHTMLSAXHandler, NULL);
        htmlCtxtReadFile(ctxt, filename, NULL, options);
        htmlFreeParserCtxt(ctxt);
	ret = 0;
    } else
#endif
    {
        debugContext userData;
        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));
        }
        userData.filename = filename;
        userData.generalEntities = xmlHashCreate(0);
        userData.parameterEntities = xmlHashCreate(0);
        ctxt->userData = &userData;
        xmlCtxtUseOptions(ctxt, options);
        xmlParseDocument(ctxt);
        ret = ctxt->wellFormed ? 0 : ctxt->errNo;
        xmlHashFree(userData.generalEntities, hashFreeEntity);
        xmlHashFree(userData.parameterEntities, hashFreeEntity);
        xmlFreeDoc(ctxt->myDoc);
        xmlFreeParserCtxt(ctxt);
    }
    fclose(SAXdebug);
    if (compareFiles(temp, result)) {
        fprintf(stderr, "Got a difference for %s\n", filename);
        ret = 1;
    }

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

    return(ret);
}

#if defined(LIBXML_HTML_ENABLED) && defined(LIBXML_PUSH_ENABLED)
typedef struct {
    int dataState;
    int inCharacters;
    const xmlChar *startTag;
} xmlTokenizerConfig;

static void
startDocumentTokenizer(void *ctx) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;

    ctxt->instate = XML_PARSER_CONTENT;

    if (config->dataState != 0) {
        ctxt->endCheckState = config->dataState;
        ctxt->name = config->startTag;
    }
}

static void
pendingTokenizer(xmlTokenizerConfig *config) {
    if (config->inCharacters) {
        fprintf(SAXdebug, "\n");
        config->inCharacters = 0;
    }
}

static void
internalSubsetTokenizer(void *ctx, const xmlChar *name,
                        const xmlChar *publicId, const xmlChar *systemId) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;

    pendingTokenizer(config);

    fprintf(SAXdebug, "DOCTYPE\n%s\n%s\n%s\n",
            name ? name : BAD_CAST "<none>",
            publicId ? publicId : BAD_CAST "<none>",
            systemId ? systemId : BAD_CAST "<none>");
}

static void
startElementTokenizer(void *ctx, const xmlChar *name, const xmlChar **atts) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;
    int i;

    pendingTokenizer(config);

    fprintf(SAXdebug, "StartTag\n%s", name);
    if (atts != NULL) {
        for (i = 0; atts[i] != NULL; i += 2) {
	    fprintf(SAXdebug, " %s=", atts[i]);
            if (atts[i+1] != NULL)
	        fprintf(SAXdebug, "%s", atts[i+1]);
        }
    }
    fprintf(SAXdebug, "\n");
}

static void
endElementTokenizer(void *ctx, const xmlChar *name) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;

    pendingTokenizer(config);

    fprintf(SAXdebug, "EndTag\n%s\n", name);
}

static void
charactersTokenizer(void *ctx, const xmlChar *ch, int len) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;

    if (!config->inCharacters) {
        fprintf(SAXdebug, "Character\n");
        config->inCharacters = 1;
    }

    fwrite(ch, 1, len, SAXdebug);
}

static void
commentTokenizer(void *ctx, const xmlChar *value) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;

    pendingTokenizer(config);

    fprintf(SAXdebug, "Comment\n%s\n", value);
}

static void
endDocumentTokenizer(void *ctx) {
    xmlParserCtxtPtr ctxt = ctx;
    xmlTokenizerConfig *config = ctxt->_private;

    pendingTokenizer(config);
}

static xmlSAXHandler tokenizeHtmlSAXHandler = {
    internalSubsetTokenizer,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    startDocumentTokenizer,
    endDocumentTokenizer,
    startElementTokenizer,
    endElementTokenizer,
    NULL,
    charactersTokenizer,
    NULL,
    NULL,
    commentTokenizer,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    1,
    NULL,
    NULL,
    NULL,
    NULL
};

/**
 * Parse a file using the SAX API and check for errors.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @returns 0 in case of success, an error code otherwise
 */
static int
htmlTokenizerTest(const char *filename, const char *result,
                  const char *err ATTRIBUTE_UNUSED,
                  int options) {
    xmlTokenizerConfig config;
    char startTag[31];
    FILE *input;
    char *temp;
    unsigned testNum, dataState, size;
    int ret = 0, counter = 0;

    nb_tests++;
    temp = resultFilename(filename, temp_directory, ".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);
	xmlFree(temp);
	return(-1);
    }

    input = fopen(filename, "rb");
    if (input == NULL) {
        fprintf(stderr, "%s: failed to open\n", filename);
        return(-1);
    }

    while (fscanf(input, "%u %30s %u %u%*1[\n]",
                  &testNum, startTag, &dataState, &size) >= 4) {
        htmlParserCtxtPtr ctxt;
        char *data;

        fprintf(SAXdebug, "%d\n", counter++);

        data = xmlMalloc(size + 1);
        if (fread(data, 1, size, input) != size) {
            fprintf(stderr, "%s:%d: unexpected eof\n", filename, counter);
            return(-1);
        }

        ctxt = htmlCreatePushParserCtxt(&tokenizeHtmlSAXHandler, NULL, NULL, 0,
                                        NULL, XML_CHAR_ENCODING_UTF8);
        config.dataState = dataState;
        config.startTag = BAD_CAST startTag;
        config.inCharacters = 0;
        ctxt->_private = &config;
        /* Skip charset auto-detection */
        ctxt->instate = XML_PARSER_XML_DECL;
        htmlCtxtUseOptions(ctxt, options | HTML_PARSE_HTML5);
        htmlParseChunk(ctxt, data, size, 1);
        htmlFreeParserCtxt(ctxt);

        xmlFree(data);
    }
    if (!feof(input)) {
        fprintf(stderr, "%s:%d: invalid format\n", filename, counter);
        return(-1);
    }

    fclose(input);
    fclose(SAXdebug);

    if (compareFiles(temp, result)) {
        fprintf(stderr, "Got a difference for %s\n", filename);
        ret = 1;
    }

    if (temp != NULL) {
        unlink(temp);
        xmlFree(temp);
    }

    return(ret);
}
#endif /* HTML */

/************************************************************************
 *									*
 *		Parse to tree based tests				*
 *									*
 ************************************************************************/
/**
 * Parse a file using the old #xmlParseFile API, then serialize back
 * reparse the result and serialize again, then check for deviation
 * in serialization.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages: unused
 * @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
    xmlGetWarningsDefaultValue = 0;
    doc = xmlParseFile(filename);
    xmlGetWarningsDefaultValue = 1;
#else
    doc = xmlReadFile(filename, NULL, XML_PARSE_NOWARNING);
#endif
    if (doc == NULL)
        return(1);
    temp = resultFilename(filename, temp_directory, ".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
    xmlGetWarningsDefaultValue = 0;
    doc = xmlParseFile(temp);
    xmlGetWarningsDefaultValue = 1;
#else
    doc = xmlReadFile(temp, NULL, XML_PARSE_NOWARNING);
#endif
    if (doc == NULL)
        return(1);
    xmlSaveFile(temp, doc);
    if (compareFiles(temp, result)) {
        res = 1;
    }
    xmlFreeDoc(doc);

    if (temp != NULL) {
        unlink(temp);
        xmlFree(temp);
    }

    return(res);
}

#ifdef LIBXML_PUSH_ENABLED
/**
 * Parse a file using the Push API, then serialize back
 * to check for content.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages: unused
 * @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);
    xmlCtxtSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
    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);
    xmlFree((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);
}

static int pushBoundaryCount;
static int pushBoundaryRefCount;
static int pushBoundaryCharsCount;
static int pushBoundaryCDataCount;

static void
internalSubsetBnd(void *ctx, const xmlChar *name, const xmlChar *externalID,
                  const xmlChar *systemID) {
    pushBoundaryCount++;
    xmlSAX2InternalSubset(ctx, name, externalID, systemID);
}

static void
referenceBnd(void *ctx, const xmlChar *name) {
    pushBoundaryRefCount++;
    xmlSAX2Reference(ctx, name);
}

static void
charactersBnd(void *ctx, const xmlChar *ch, int len) {
    pushBoundaryCount++;
    pushBoundaryCharsCount++;
    xmlSAX2Characters(ctx, ch, len);
}

static void
cdataBlockBnd(void *ctx, const xmlChar *ch, int len) {
    pushBoundaryCount++;
    pushBoundaryCDataCount++;
    xmlSAX2CDataBlock(ctx, ch, len);
}

static void
processingInstructionBnd(void *ctx, const xmlChar *target,
                         const xmlChar *data) {
    pushBoundaryCount++;
    xmlSAX2ProcessingInstruction(ctx, target, data);
}

static void
commentBnd(void *ctx, const xmlChar *value) {
    xmlParserCtxtPtr ctxt = ctx;
    if (ctxt->inSubset == 0)
        pushBoundaryCount++;
    xmlSAX2Comment(ctx, value);
}

static void
startElementBnd(void *ctx, const xmlChar *xname, const xmlChar **atts) {
    const char *name = (const char *)xname;

    /* Some elements might be created automatically. */
    if ((strcmp(name, "html") != 0) &&
        (strcmp(name, "body") != 0) &&
        (strcmp(name, "head") != 0) &&
        (strcmp(name, "p") != 0)) {
        pushBoundaryCount++;
    }
    xmlSAX2StartElement(ctx, xname, atts);
}

static void
endElementBnd(void *ctx, const xmlChar *name) {
    /*pushBoundaryCount++;*/
    xmlSAX2EndElement(ctx, name);
}

static void
startElementNsBnd(void *ctx, const xmlChar *localname, const xmlChar *prefix,
                  const xmlChar *URI, int nb_namespaces,
                  const xmlChar **namespaces, int nb_attributes,
                  int nb_defaulted, const xmlChar **attributes) {
    pushBoundaryCount++;
    xmlSAX2StartElementNs(ctx, localname, prefix, URI, nb_namespaces,
                          namespaces, nb_attributes, nb_defaulted, attributes);
}

static void
endElementNsBnd(void *ctx, const xmlChar *localname, const xmlChar *prefix,
                const xmlChar *URI) {
    /*pushBoundaryCount++;*/
    xmlSAX2EndElementNs(ctx, localname, prefix, URI);
}

/**
 * Test whether the push parser detects boundaries between syntactical
 * elements correctly.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages: unused
 * @returns 0 in case of success, an error code otherwise
 */
static int
pushBoundaryTest(const char *filename, const char *result,
                 const char *err ATTRIBUTE_UNUSED,
                 int options) {
    xmlParserCtxtPtr ctxt;
    xmlDocPtr doc;
    xmlSAXHandler bndSAX;
    const char *base;
    int size, res, numCallbacks;
    int cur = 0;
    unsigned long avail, oldConsumed, consumed;

    /*
     * HTML encoding detection doesn't work when data is fed bytewise.
     */
    if (strcmp(filename, "./test/HTML/xml-declaration-1.html") == 0)
        return(0);

    /*
     * If the parser made progress, check that exactly one construct was
     * processed and that the input buffer is (almost) empty.
     * Since we use a chunk size of 1, this tests whether content is
     * processed as early as possible.
     */

    nb_tests++;

    memset(&bndSAX, 0, sizeof(bndSAX));
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
        xmlSAX2InitHtmlDefaultSAXHandler(&bndSAX);
        bndSAX.startElement = startElementBnd;
        bndSAX.endElement = endElementBnd;
    } else
#endif
    {
        xmlSAXVersion(&bndSAX, 2);
        bndSAX.startElementNs = startElementNsBnd;
        bndSAX.endElementNs = endElementNsBnd;
    }

    bndSAX.internalSubset = internalSubsetBnd;
    bndSAX.reference = referenceBnd;
    bndSAX.characters = charactersBnd;
    bndSAX.cdataBlock = cdataBlockBnd;
    bndSAX.processingInstruction = processingInstructionBnd;
    bndSAX.comment = commentBnd;

    /*
     * 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);
    }

#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML)
	ctxt = htmlCreatePushParserCtxt(&bndSAX, NULL, base, 1, filename,
	                                XML_CHAR_ENCODING_NONE);
    else
#endif
    ctxt = xmlCreatePushParserCtxt(&bndSAX, NULL, base, 1, filename);
    xmlCtxtSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
    xmlCtxtUseOptions(ctxt, options);
    cur = 1;
    consumed = 0;
    numCallbacks = 0;
    avail = 0;
    while ((cur < size) && (numCallbacks <= 1) && (avail <= 0)) {
        int terminate = (cur + 1 >= size);
        int isText = 0;

        if (ctxt->instate == XML_PARSER_CONTENT) {
            int firstChar = (ctxt->input->end > ctxt->input->cur) ?
                            *ctxt->input->cur :
                            base[cur];

            if (options & XML_PARSE_HTML) {
                isText = ((ctxt->endCheckState) || (firstChar != '<'));
            } else {
                isText = ((firstChar != '<') && (firstChar != '&'));
            }
        }

        oldConsumed = ctxt->input->consumed +
                      (unsigned long) (ctxt->input->cur - ctxt->input->base);

        pushBoundaryCount = 0;
        pushBoundaryRefCount = 0;
        pushBoundaryCharsCount = 0;
        pushBoundaryCDataCount = 0;

#ifdef LIBXML_HTML_ENABLED
        if (options & XML_PARSE_HTML)
            htmlParseChunk(ctxt, base + cur, 1, terminate);
        else
#endif
        xmlParseChunk(ctxt, base + cur, 1, terminate);
	cur += 1;

        /*
         * Callback check: Check that only a single construct was parsed.
         */
        if (pushBoundaryRefCount > 0) {
            numCallbacks = 1;
        } else {
            numCallbacks = pushBoundaryCount;
            if (pushBoundaryCharsCount > 1) {
                if (options & XML_PARSE_HTML) {
                    /*
                     * The HTML parser can generate a mix of chars and
                     * references.
                     */
                    numCallbacks -= pushBoundaryCharsCount - 1;
                } else {
                    /*
                     * Allow two chars callbacks. This can happen when
                     * multi-byte chars are split across buffer boundaries.
                     */
                    numCallbacks -= 1;
                }
            }
            if (options & XML_PARSE_HTML) {
                /*
                 * Allow multiple cdata callbacks in HTML mode.
                 */
                if (pushBoundaryCDataCount > 1)
                    numCallbacks -= pushBoundaryCDataCount - 1;
            }
        }

        /*
         * Buffer check: If input was consumed, check that the input
         * buffer is (almost) empty.
         */
        consumed = ctxt->input->consumed +
                   (unsigned long) (ctxt->input->cur - ctxt->input->base);
        if ((ctxt->instate != XML_PARSER_DTD) &&
            (consumed >= 4) &&
            (consumed != oldConsumed)) {
            size_t max = 0;

            avail = ctxt->input->end - ctxt->input->cur;

            if ((options & XML_PARSE_HTML) &&
                (ctxt->instate == XML_PARSER_END_TAG)) {
                /* Something related to script parsing. */
                max = 3;
            } else if (isText) {
                int c = *ctxt->input->cur;

                if ((options & XML_PARSE_HTML) &&
                    (ctxt->endCheckState)) {
                    max = strlen((const char *) ctxt->name) + 2;
                } else if (c == '&') {
                    max = (options & XML_PARSE_HTML) ? 32 : 1;
                } else if (c == '<') {
                    max = 1;
                } else {
                    /* 3 bytes for partial UTF-8 */
                    max = 3;
                }
            } else if (ctxt->instate == XML_PARSER_CDATA_SECTION) {
                /* 2 bytes for terminator, 3 bytes for UTF-8 */
                max = 5;
            }

            if (avail <= max)
                avail = 0;
        }
    }
    doc = ctxt->myDoc;
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML)
        res = 1;
    else
#endif
    res = ctxt->wellFormed;
    xmlFreeParserCtxt(ctxt);
    xmlFree((char *)base);
    if (numCallbacks > 1) {
	xmlFreeDoc(doc);
	fprintf(stderr, "Failed push boundary callback test (%d@%lu-%lu): %s\n",
                numCallbacks, oldConsumed, consumed, filename);
	return(-1);
    }
    if (avail > 0) {
	xmlFreeDoc(doc);
	fprintf(stderr, "Failed push boundary buffer test (%lu@%lu): %s\n",
                avail, consumed, filename);
	return(-1);
    }
    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

static char *
dumpNodeList(xmlNodePtr list) {
    xmlBufferPtr buffer;
    xmlSaveCtxtPtr save;
    xmlNodePtr cur;
    char *ret;

    buffer = xmlBufferCreate();
    save = xmlSaveToBuffer(buffer, "UTF-8", 0);
    for (cur = list; cur != NULL; cur = cur->next)
        xmlSaveTree(save, cur);
    xmlSaveClose(save);

    ret = (char *) xmlBufferDetach(buffer);
    xmlBufferFree(buffer);
    return(ret);
}

static int
testParseContent(xmlParserCtxtPtr ctxt, xmlDocPtr doc, const char *filename) {
    xmlParserInputPtr input;
    xmlNodePtr root = NULL, list;
    char *content, *roundTrip;
    int ret = 0;

    /*
     * Undeclared entities aren't a fatal error if there's an
     * external DTD. When parsing content, we assume there's no
     * DTD, so the undeclared entity test would fail.
     */
    if (strcmp(filename, "./test/undeclared-entity.xml") == 0)
        return 0;

    if (ctxt->html) {
        xmlNodePtr cur;

        if (doc == NULL || doc->children == NULL)
            return 0;
        for (cur = doc->children->children; cur != NULL; cur = cur->next) {
            if (xmlStrEqual(cur->name, BAD_CAST "body")) {
                root = cur;
                break;
            }
        }
    } else {
        root = xmlDocGetRootElement(doc);
    }
    if (root == NULL)
        return 0;

    content = dumpNodeList(root->children);

    input = xmlNewInputFromString(NULL, content, XML_INPUT_BUF_STATIC);
    list = xmlCtxtParseContent(ctxt, input, root, 0);
    roundTrip = dumpNodeList(list);
    if (strcmp(content, roundTrip) != 0) {
        fprintf(stderr, "xmlCtxtParseContent failed for %s\n", filename);
        ret = -1;
    }
    xmlFree(roundTrip);
    xmlFreeNodeList(list);

    /* xmlParseInNodeContext uses the document's encoding. */
    xmlFree(doc->encoding);
    doc->encoding = xmlStrdup(BAD_CAST "UTF-8");
    xmlParseInNodeContext(root, content, strlen(content),
                          ctxt->options | XML_PARSE_NOERROR,
                          &list);
    roundTrip = dumpNodeList(list);
    if (strcmp(content, roundTrip) != 0) {
        fprintf(stderr, "xmlParseInNodeContext failed for %s\n", filename);
        ret = -1;
    }
    xmlFree(roundTrip);
    xmlFreeNodeList(list);

    xmlFree(content);
    return(ret);
}

/**
 * Parse a file using the old #xmlReadMemory API, then serialize back
 * reparse the result and serialize again, then check for deviation
 * in serialization.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages: unused
 * @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) {
    xmlParserCtxtPtr ctxt;
    xmlDocPtr doc;
    const char *base;
    int size, res;
    int ret = 0;

    options |= XML_PARSE_NOWARNING;

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

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

    if (testParseContent(ctxt, doc, filename) < 0)
	ret = -1;

    xmlFreeDoc(doc);
    xmlFreeParserCtxt(ctxt);
    xmlFree((char *)base);
    return(ret);
}

/**
 * Parse a file with entity resolution, then serialize back
 * reparse the result and serialize again, then check for deviation
 * in serialization.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages: unused
 * @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 | XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
    if (doc == NULL)
        return(1);
    temp = resultFilename(filename, temp_directory, ".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 | XML_PARSE_NOWARNING | XML_PARSE_NOERROR);
    if (doc == NULL)
        return(1);
    xmlSaveFile(temp, doc);
    if (compareFiles(temp, result)) {
        res = 1;
    }
    xmlFreeDoc(doc);

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

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

    nb_tests++;
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
        ctxt = htmlNewParserCtxt();
        xmlCtxtSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
        doc = htmlCtxtReadFile(ctxt, filename, NULL, options);
    } else
#endif
    {
        ctxt = xmlNewParserCtxt();
        xmlCtxtSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
	doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
#ifdef LIBXML_XINCLUDE_ENABLED
        if (options & XML_PARSE_XINCLUDE) {
            xmlXIncludeCtxtPtr xinc = NULL;

            xinc = xmlXIncludeNewContext(doc);
            xmlXIncludeSetErrorHandler(xinc, testStructuredErrorHandler, NULL);
            xmlXIncludeSetFlags(xinc, options);
            if (xmlXIncludeProcessNode(xinc, (xmlNodePtr) doc) < 0) {
                testErrorHandler(NULL, "%s : failed to parse\n", filename);
                xmlFreeDoc(doc);
                doc = NULL;
            }
            xmlXIncludeFreeContext(xinc);
        }
#endif
    }
    if (result) {
        xmlChar *base = NULL;

	if (doc == NULL) {
	    base = xmlStrdup(BAD_CAST "");
	    size = 0;
	} else {
#ifdef LIBXML_HTML_ENABLED
	    if (options & XML_PARSE_HTML) {
		htmlDocDumpMemory(doc, &base, &size);
	    } else
#endif
	    xmlDocDumpMemory(doc, &base, &size);
	}
	res = compareFileMem(result, (char *) base, size);
	xmlFree(base);
    }

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

    if (testParseContent(ctxt, doc, filename) < 0)
	ret = -1;

    xmlFreeDoc(doc);
    xmlFreeParserCtxt(ctxt);
    return(ret);
}

#if defined(LIBXML_VALID_ENABLED) || defined(LIBXML_HTML_ENABLED)
/**
 * Parse a file using the #xmlReadFd API and check for errors.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @returns 0 in case of success, an error code otherwise
 */
static int
fdParseTest(const char *filename, const char *result, const char *err,
             int options) {
    xmlParserCtxtPtr ctxt;
    xmlDocPtr doc;
    const char *base = NULL;
    int size, res = 0, fd;

    nb_tests++;
    fd = open(filename, RD_FLAGS);
#ifdef LIBXML_HTML_ENABLED
    if (options & XML_PARSE_HTML) {
        ctxt = htmlNewParserCtxt();
        xmlCtxtSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
        doc = htmlCtxtReadFd(ctxt, fd, filename, NULL, options);
        htmlFreeParserCtxt(ctxt);
    } else
#endif
    {
        ctxt = xmlNewParserCtxt();
        xmlCtxtSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
	doc = xmlCtxtReadFd(ctxt, fd, filename, NULL, options);
        xmlFreeParserCtxt(ctxt);
    }
    close(fd);
    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 (doc != NULL) {
	if (base != NULL)
	    xmlFree((char *)base);
	xmlFreeDoc(doc);
    }
    if (res != 0) {
        fprintf(stderr, "Result for %s failed in %s\n", filename, result);
        return(-1);
    }
    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);
}
#endif


#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, temp_directory, ".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);
	    xmlFree(temp);
	    return(-1);
	}
    }
#ifdef LIBXML_RELAXNG_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);
                xmlFree(temp);
            }
	    return(0);
	}
    }
#endif
    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);
	}
    }
    if (t != NULL) {
        fclose(t);
	ret = compareFiles(temp, result);
        if (temp != NULL) {
            unlink(temp);
            xmlFree(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);
}

#ifdef __linux__

/**
 * Parse a file using the reader for Fd and checks that the corresponding fd is
 * correctly closed and there's no memory leak
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @returns 0 in case of success, an error code otherwise
 */
static int
xmlReaderForFdTest(const char *filename, const char *result ATTRIBUTE_UNUSED,
                   const char *err ATTRIBUTE_UNUSED, int options ATTRIBUTE_UNUSED) {
    xmlTextReaderPtr reader;
    int fd = 0;
    int ret;

    fd = open(filename, O_RDONLY, 0);
    if (fd == -1) {
        fprintf(stderr, "Error opening test file %s\n", filename);
        return 1;
    }

    reader = xmlReaderForFd(fd, filename, NULL, 0);
    if (reader) {
        xmlFreeTextReader(reader);
    } else {
        fprintf(stderr, "Error creating reader for file %s\n", filename);
        ret = 1;
    }

    /* Ensure that no new fd was created and never closed */
    ret = fcntl(fd + 1, F_GETFD);
    if (ret != -1) {
        fprintf(stderr, "File descriptor %d was not closed correctly %s\n", fd + 1, filename);
    } else {
        ret = 0;
    }

    /* Ensure that the manual open fd is still open */
    ret = fcntl(fd, F_GETFD);
    if (ret != 0) {
        fprintf(stderr, "File descriptor %d was early closed correctly %s\n", fd, filename);
    }

    close(fd);

    /* Check if we close the fd correctly */
    ret = fcntl(fd, F_GETFD);
    if (ret != -1) {
        fprintf(stderr, "File descriptor %d was not closed correctly %s\n", fd, filename);
    } else {
        ret = 0;
    }

    return(ret);
}

#if defined(LIBXML_ZLIB_ENABLED) && defined(LIBXML_OUTPUT_ENABLED)
static int
xmllintGzWrite(void *ctxt, const char *buf, int len) {
    return gzwrite(ctxt, buf, len);
}

static int
xmllintGzClose(void *ctxt) {
    if (gzclose(ctxt) != Z_OK)
        return -1;

    return 0;
}

/**
 * Parse a file using the reader for Fd and checks that the corresponding fd is
 * correctly closed and there's no memory leak
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @returns 0 in case of success, an error code otherwise
 */
static int
xmlReaderForFdGzTest(const char *filename, const char *result ATTRIBUTE_UNUSED,
                     const char *err ATTRIBUTE_UNUSED, int options ATTRIBUTE_UNUSED) {
    /** Gzip the file and test again to verify the zipper branch */
    char tmp[500] = {0};
    gzFile gz = NULL;
    xmlSaveCtxtPtr ctxt = NULL;
    xmlDocPtr doc = NULL;
    int ret = 0;

    if (snprintf(tmp, 499, "%s.gz", baseFilename(filename)) >= 499) {
        tmp[499] = 0;
    }

    gz = gzopen(tmp, "wb9");
    if (gz == NULL) {
        fprintf(stderr, "Error creating gz file %s\n", tmp);
        return 1;
    }

    doc = xmlReadFile(filename, NULL, 0);
    if (doc == NULL) {
        fprintf(stderr, "Error reading test doc %s\n", filename);
        gzclose(gz);
        return 1;
    }
    ctxt = xmlSaveToIO(xmllintGzWrite, xmllintGzClose, gz, NULL, 0);
    if (ctxt == NULL) {
        fprintf(stderr, "Error creating save contxt %s\n", tmp);
        xmlFreeDoc(doc);
        gzclose(gz);
        return 1;
    }

    if (xmlSaveDoc(ctxt, doc) < 0) {
        fprintf(stderr, "Error saving file %s\n", tmp);
        xmlSaveClose(ctxt);
        xmlFreeDoc(doc);
        return 1;
    }

    xmlSaveClose(ctxt);
    xmlFreeDoc(doc);

    ret = xmlReaderForFdTest(tmp, NULL, NULL, 0);
    unlink(tmp);

    return(ret);
}
#endif

#endif

/**
 * Parse a file using the reader API and check for errors.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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);
    xmlTextReaderSetStructuredErrorHandler(reader, testStructuredErrorHandler,
                                           NULL);
    ret = streamProcessTest(filename, result, err, reader, NULL, options);
    xmlFreeTextReader(reader);
    return(ret);
}

/**
 * Parse a file using the walker, i.e. a reader built from a atree.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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 | XML_PARSE_NOWARNING);
    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);
}

/**
 * Parse a file using the reader API from memory and check for errors.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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);
    xmlTextReaderSetStructuredErrorHandler(reader, testStructuredErrorHandler,
                                           NULL);
    ret = streamProcessTest(filename, result, err, reader, NULL, options);
    xmlFree((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
testXPath(const char *str, int xptr, int expr) {
    xmlXPathObjectPtr res;
    xmlXPathContextPtr ctxt;

    nb_tests++;
#if defined(LIBXML_XPTR_ENABLED)
    if (xptr) {
	ctxt = xmlXPathNewContext(xpathDocument);
        xmlXPathSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
	res = xmlXPtrEval(BAD_CAST str, ctxt);
    } else {
#endif
	ctxt = xmlXPathNewContext(xpathDocument);
        xmlXPathSetErrorHandler(ctxt, testStructuredErrorHandler, NULL);
	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);
}

/**
 * Parse a file containing XPath standalone expressions and evaluate them
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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, temp_directory, ".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);
        xmlFree(temp);
	return(-1);
    }

    input = fopen(filename, "rb");
    if (input == NULL) {
        fprintf(stderr,
		"Cannot open %s for reading\n", filename);
        xmlFree(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);
        xmlFree(temp);
    }
    return(ret);
}

/**
 * Parse a file containing XPath standalone expressions and evaluate them
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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));
}

/**
 * Parse a file containing XPath expressions and evaluate them against
 * a set of corresponding documents.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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
/**
 * Parse a file containing XPath expressions and evaluate them against
 * a set of corresponding documents.
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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 ATTRIBUTE_UNUSED) {

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

    xpathDocument = xmlReadFile(filename, NULL,
                                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 */

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

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

    temp = resultFilename(filename, temp_directory, ".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);
        xmlFree(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);
        xmlFree(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_VALID_ENABLED */

#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);
}

/**
 * Parse a file containing URI and check for errors
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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, temp_directory, ".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);
        xmlFree(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);
            xmlFree(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);
        xmlFree(temp);
    }
    return(res);
}

/**
 * Parse a file containing URI and check for errors
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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));
}

/**
 * Parse a file containing URI, compose them against a fixed base and
 * check for errors
 *
 * @param filename  the file to parse
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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;

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

/**
 * Returns a pointer to the urip: query handler, in this example simply
 * the urip_current pointer...
 *
 * @param URI  an URI to test
 * @returns an Input context or NULL in case or error
 */
static void *
uripOpen(const char * URI) {
#ifdef LIBXML_CATALOG_ENABLED
    if ((URI == NULL) ||
        (!strcmp(URI, "file://" XML_SYSCONFDIR "/xml/catalog")))
        return(NULL);
#endif
    /* 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);
}

/**
 * Close the urip: query handler
 *
 * @param context  the read context
 * @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);
}

/**
 * Implement an urip: query read.
 *
 * @param context  the read context
 * @param buffer  where to store data
 * @param len  number of bytes to 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);
}

/**
 * Run a set of tests to check how Path and URI are handled before
 * being passed to the I/O layer
 *
 * @param filename  ignored
 * @param result  ignored
 * @param err  ignored
 * @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);
}

/************************************************************************
 *									*
 *			Schemas tests					*
 *									*
 ************************************************************************/

#ifdef LIBXML_SCHEMAS_ENABLED
static int
schemasOneTest(const char *sch,
               const char *filename,
               const char *err,
	       int options,
	       xmlSchemaPtr schemas) {
    int ret = 0;
    int i;
    int parseErrorsSize = testErrorsSize;

    /*
     * Test both memory and streaming validation.
     */
    for (i = 0; i < 2; i++) {
        xmlSchemaValidCtxtPtr ctxt;
        int validResult = 0;

        testErrorsSize = parseErrorsSize;
        testErrors[parseErrorsSize] = 0;

        ctxt = xmlSchemaNewValidCtxt(schemas);
        xmlSchemaSetValidStructuredErrors(ctxt, testStructuredErrorHandler,
                                          NULL);

        if (i == 0) {
            xmlDocPtr doc;

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

        if (validResult == 0) {
            testErrorHandler(NULL, "%s validates\n", filename);
        } else if (validResult > 0) {
            testErrorHandler(NULL, "%s fails to validate\n", filename);
        } else {
            testErrorHandler(NULL, "%s validation generated an internal "
                             "error\n", filename);
        }

        xmlSchemaFreeValidCtxt(ctxt);

        if (compareFileMem(err, testErrors, testErrorsSize)) {
            fprintf(stderr, "Error for %s on %s failed in round %d: %s\n",
                filename, sch, i, testErrors);
            ret = 1;
        }
    }

    return(ret);
}
/**
 * Parse a file containing URI, compose them against a fixed base and
 * check for errors
 *
 * @param filename  the schemas file
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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;
    int parseErrorsSize;
    char pattern[500];
    char prefix[500];
    char err[500];
    glob_t globbuf;
    size_t i;
    char count = 0;

    /* first compile the schemas if possible */
    ctxt = xmlSchemaNewParserCtxt(filename);
    xmlSchemaSetParserStructuredErrors(ctxt, testStructuredErrorHandler, NULL);
    schemas = xmlSchemaParse(ctxt);
    xmlSchemaFreeParserCtxt(ctxt);
    parseErrorsSize = testErrorsSize;

    /*
     * 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 = parseErrorsSize;
        testErrors[parseErrorsSize] = 0;
        instance = globbuf.gl_pathv[i];
	base2 = baseFilename(instance);
	len = strlen(base2);
	if ((len > 6) && (base2[len - 6] == '_')) {
	    count = base2[len - 5];
	    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;
	}

        nb_tests++;
        if (schemas == NULL) {
            /* schema load failed, verify the error message*/
            if (compareFileMem(err, testErrors, testErrorsSize)) {
                fprintf(stderr, "Error for %s failed\n", filename);
                res = 1;
            }
        } else {
            ret = schemasOneTest(filename, instance, err, options, schemas);
            if (ret != 0)
                res = ret;
        }
    }
    globfree(&globbuf);
    xmlSchemaFree(schemas);

    return(res);
}

/**
 * Validate an XML instance using XSI-based dynamic schema assembly.
 * The validation context is created with a NULL schema, so the schema
 * is discovered from xsi:schemaLocation in the instance document.
 *
 * @param filename  the XML instance file
 * @param result  unused
 * @param err  unused
 * @param options  parser options
 * @returns 0 in case of success, an error code otherwise
 */
static int
schemasXsiTest(const char *filename,
               const char *resul ATTRIBUTE_UNUSED,
               const char *errr ATTRIBUTE_UNUSED,
               int options) {
    const char *base = baseFilename(filename);
    int len, ret;
    char err[500];
    char prefix[500];

    len = strlen(base);
    if ((len > 499) || (len < 5))
        return(-1);
    memcpy(prefix, base, len - 4); /* remove .xml */
    prefix[len - 4] = 0;

    ret = snprintf(err, 499, "result/schemas/%s_0.err", prefix);
    if (ret >= 499)
        err[499] = 0;

    nb_tests++;
    return(schemasOneTest(filename, filename, err, options, NULL));
}
#endif /* LIBXML_SCHEMAS_ENABLED */

/************************************************************************
 *									*
 *			RELAX NG tests					*
 *									*
 ************************************************************************/

#ifdef LIBXML_RELAXNG_ENABLED
static int
rngOneTest(const char *sch,
               const char *filename,
	       int options,
	       xmlRelaxNGPtr schemas) {
    xmlDocPtr doc;
    xmlRelaxNGValidCtxtPtr ctxt;
    int ret = 0;

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

    ctxt = xmlRelaxNGNewValidCtxt(schemas);
    xmlRelaxNGSetValidStructuredErrors(ctxt, testStructuredErrorHandler, NULL);
    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);
    }

    xmlRelaxNGFreeValidCtxt(ctxt);
    xmlFreeDoc(doc);
    return(0);
}
/**
 * Parse an RNG schemas and then apply it to the related .xml
 *
 * @param filename  the schemas file
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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;
    int parseErrorsSize;
    char pattern[500];
    char prefix[500];
    char err[500];
    glob_t globbuf;
    size_t i;
    char count = 0;

    /* first compile the schemas if possible */
    ctxt = xmlRelaxNGNewParserCtxt(filename);
    xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler,
                                        NULL);
    schemas = xmlRelaxNGParse(ctxt);
    xmlRelaxNGFreeParserCtxt(ctxt);
    if (schemas == NULL)
        testErrorHandler(NULL, "Relax-NG schema %s failed to compile\n",
                         filename);
    parseErrorsSize = testErrorsSize;

    /*
     * 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 = parseErrorsSize;
        testErrors[parseErrorsSize] = 0;
        instance = globbuf.gl_pathv[i];
	base2 = baseFilename(instance);
	len = strlen(base2);
	if ((len > 6) && (base2[len - 6] == '_')) {
	    count = base2[len - 5];
	    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) {
	    nb_tests++;
	    res = rngOneTest(filename, instance, options, schemas);
	    if (res != 0)
		ret = res;
	}
        if (compareFileMem(err, testErrors, testErrorsSize)) {
            fprintf(stderr, "Error for %s on %s failed\n", instance,
                    filename);
            ret = 1;
        }
    }
    globfree(&globbuf);
    xmlRelaxNGFree(schemas);

    return(ret);
}

/**
 * Parse an RNG schemas with a custom RNG_INCLUDE_LIMIT
 *
 * @param filename  the schemas file
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @returns 0 in case of success, an error code otherwise
 */
static int
rngIncludeTest(const char *filename,
               const char *resul ATTRIBUTE_UNUSED,
               const char *errr ATTRIBUTE_UNUSED,
               int options ATTRIBUTE_UNUSED) {
    xmlRelaxNGParserCtxtPtr ctxt;
    xmlRelaxNGPtr schemas;
    int ret = 0;

    /* first compile the schemas if possible */
    ctxt = xmlRelaxNGNewParserCtxt(filename);
    xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler,
                                        NULL);

    /* Should work */
    schemas = xmlRelaxNGParse(ctxt);
    if (schemas == NULL) {
        testErrorHandler(NULL, "Relax-NG schema %s failed to compile\n",
                         filename);
        ret = -1;
        goto done;
    }
    xmlRelaxNGFree(schemas);
    xmlRelaxNGFreeParserCtxt(ctxt);

    ctxt = xmlRelaxNGNewParserCtxt(filename);
    /* Should fail */
    xmlRelaxParserSetIncLImit(ctxt, 2);
    xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler,
                                        NULL);
    schemas = xmlRelaxNGParse(ctxt);
    if (schemas != NULL) {
        ret = -1;
        xmlRelaxNGFree(schemas);
    }
    xmlRelaxNGFreeParserCtxt(ctxt);

    ctxt = xmlRelaxNGNewParserCtxt(filename);
    /* Should work */
    xmlRelaxParserSetIncLImit(ctxt, 3);
    xmlRelaxNGSetParserStructuredErrors(ctxt, testStructuredErrorHandler,
                                        NULL);
    schemas = xmlRelaxNGParse(ctxt);
    if (schemas == NULL) {
        testErrorHandler(NULL, "Relax-NG schema %s failed to compile\n",
                         filename);
        ret = -1;
        goto done;
    }
    xmlRelaxNGFree(schemas);

done:
    xmlRelaxNGFreeParserCtxt(ctxt);
    return(ret);
}

#ifdef LIBXML_READER_ENABLED
/**
 * Parse a set of files with streaming, applying an RNG schemas
 *
 * @param filename  the schemas file
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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")) || (!strcmp(prefix, "simplifyChoiceNotAllowed")))
	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);
        xmlTextReaderSetStructuredErrorHandler(reader,
                testStructuredErrorHandler, NULL);
	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 /* LIBXML_RELAX_ENABLED */

/************************************************************************
 *									*
 *			Schematron tests				*
 *									*
 ************************************************************************/

#ifdef LIBXML_SCHEMATRON_ENABLED
static int
schematronOneTest(const char *sch, const char *filename, int options,
                  xmlSchematronPtr schematron) {
    xmlDocPtr doc;
    xmlSchematronValidCtxtPtr ctxt;
    int ret;

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

    ctxt = xmlSchematronNewValidCtxt(schematron, XML_SCHEMATRON_OUT_ERROR);
    xmlSchematronSetValidStructuredErrors(ctxt, testStructuredErrorHandler,
                                          NULL);
    ret = xmlSchematronValidateDoc(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);
    }

    xmlSchematronFreeValidCtxt(ctxt);
    xmlFreeDoc(doc);
    return(0);
}

/**
 * @param filename  the schemas file
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @returns 0 in case of success, an error code otherwise
 */
static int
schematronTest(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;
    xmlSchematronParserCtxtPtr pctxt;
    xmlSchematronPtr schematron;
    int res = 0, len, ret = 0;
    int parseErrorsSize;
    char pattern[500];
    char prefix[500];
    char err[500];
    glob_t globbuf;
    size_t i;
    char count = 0;

    /* Redirect XPath errors */
    xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler);

    pctxt = xmlSchematronNewParserCtxt(filename);
    schematron = xmlSchematronParse(pctxt);
    xmlSchematronFreeParserCtxt(pctxt);
    if (schematron == NULL)
        testErrorHandler(NULL, "Schematron schema %s failed to compile\n",
                         filename);
    parseErrorsSize = testErrorsSize;

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

    if (snprintf(pattern, 499, "./test/schematron/%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 = parseErrorsSize;
        testErrors[parseErrorsSize] = 0;
        instance = globbuf.gl_pathv[i];
	base2 = baseFilename(instance);
	len = strlen(base2);
	if ((len > 6) && (base2[len - 6] == '_')) {
	    count = base2[len - 5];
	    res = snprintf(err, 499, "result/schematron/%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 (schematron != NULL) {
	    nb_tests++;
	    res = schematronOneTest(filename, instance, options, schematron);
	    if (res != 0)
		ret = res;
	}
        if (compareFileMem(err, testErrors, testErrorsSize)) {
            fprintf(stderr, "Error for %s on %s failed\n", instance,
                    filename);
            ret = 1;
        }
    }
    globfree(&globbuf);

done:
    xmlSchematronFree(schematron);
    xmlSetStructuredErrorFunc(NULL, NULL);
    return(ret);
}
#endif /* LIBXML_SCHEMATRON_ENABLED */

#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);
}

/**
 * Parse a set of files with streaming, applying an RNG schemas
 *
 * @param filename  the schemas file
 * @param result  the file with expected result
 * @param err  the file with error messages
 * @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);
    }
    f = fopen(filename, "rb");
    if (f == NULL) {
        fprintf(stderr, "Failed to open %s\n", filename);
	return(-1);
    }
    temp = resultFilename(filename, temp_directory, ".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);
        xmlFree(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);
        xmlFree(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
     */
    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("xmlRealloc 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("xmlMalloc 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
     */
    doc = xmlReadFile(xml_filename, NULL,
            XML_PARSE_DTDATTR | XML_PARSE_NOENT | XML_PARSE_NOWARNING);
    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) xmlFree((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;
    result = xmlMemStrdup(buf);
    if (snprintf(buf, 499, "test/c14n/%s/%s.xpath", subdir, prefix) >= 499)
        buf[499] = 0;
    if (checkTestFile(buf)) {
	xpath = xmlMemStrdup(buf);
    }
    if (snprintf(buf, 499, "test/c14n/%s/%s.ns", subdir, prefix) >= 499)
        buf[499] = 0;
    if (checkTestFile(buf)) {
	ns = xmlMemStrdup(buf);
    }

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

    if (result != NULL) xmlFree(result);
    if (xpath != NULL) xmlFree(xpath);
    if (ns != NULL) xmlFree(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			*
 *									*
 ************************************************************************/

#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]);

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

    if (xmlCheckThreadLocalStorage() != 0) {
        printf("xmlCheckThreadLocalStorage failed\n");
        params->okay = 0;
        return(NULL);
    }

#ifdef LIBXML_THREAD_ALLOC_ENABLED
    xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
#endif

    myDoc = xmlReadFile(filename, NULL, XML_PARSE_NOENT | XML_PARSE_DTDLOAD);
    if (myDoc) {
        xmlFreeDoc(myDoc);
    } else {
        printf("parse failed\n");
        okay = 0;
    }
    params->okay = okay;
    return(NULL);
}

#if defined(_WIN32)
#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);
}

#else
#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);
}
#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

#if defined(LIBXML_REGEXP_ENABLED)
/************************************************************************
 *									*
 *			Regexp tests					*
 *									*
 ************************************************************************/

static void testRegexp(FILE *output, xmlRegexpPtr comp, const char *value) {
    int ret;

    ret = xmlRegexpExec(comp, (const xmlChar *) value);
    if (ret == 1)
	fprintf(output, "%s: Ok\n", value);
    else if (ret == 0)
	fprintf(output, "%s: Fail\n", value);
    else
	fprintf(output, "%s: Error: %d\n", value, ret);
}

static int
regexpTest(const char *filename, const char *result, const char *err,
	   int options ATTRIBUTE_UNUSED) {
    xmlRegexpPtr comp = NULL;
    FILE *input, *output;
    char *temp;
    char expression[5000];
    int len, ret, res = 0;

    /*
     * TODO: Custom error handler for regexp
     */
    xmlSetStructuredErrorFunc(NULL, testStructuredErrorHandler);

    nb_tests++;

    input = fopen(filename, "rb");
    if (input == NULL) {
        fprintf(stderr,
		"Cannot open %s for reading\n", filename);
	ret = -1;
        goto done;
    }
    temp = resultFilename(filename, "", ".res");
    if (temp == NULL) {
        fprintf(stderr, "Out of memory\n");
        fatalError();
    }
    output = fopen(temp, "wb");
    if (output == NULL) {
	fprintf(stderr, "failed to open output file %s\n", temp);
        xmlFree(temp);
	ret = -1;
        goto done;
    }
    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) {
	    if (expression[0] == '#')
		continue;
	    if ((expression[0] == '=') && (expression[1] == '>')) {
		char *pattern = &expression[2];

		if (comp != NULL) {
		    xmlRegFreeRegexp(comp);
		    comp = NULL;
		}
		fprintf(output, "Regexp: %s\n", pattern) ;
		comp = xmlRegexpCompile((const xmlChar *) pattern);
		if (comp == NULL) {
		    fprintf(output, "   failed to compile\n");
		    break;
		}
	    } else if (comp == NULL) {
		fprintf(output, "Regexp: %s\n", expression) ;
		comp = xmlRegexpCompile((const xmlChar *) expression);
		if (comp == NULL) {
		    fprintf(output, "   failed to compile\n");
		    break;
		}
	    } else if (comp != NULL) {
		testRegexp(output, comp, expression);
	    }
	}
    }
    fclose(output);
    fclose(input);
    if (comp != NULL)
	xmlRegFreeRegexp(comp);

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

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

done:
    xmlSetStructuredErrorFunc(NULL, NULL);

    return(res);
}

/************************************************************************
 *									*
 *			Automata tests					*
 *									*
 ************************************************************************/

static int scanNumber(char **ptr) {
    int ret = 0;
    char *cur;

    cur = *ptr;
    while ((*cur >= '0') && (*cur <= '9')) {
	ret = ret * 10 + (*cur - '0');
	cur++;
    }
    *ptr = cur;
    return(ret);
}

static int
automataTest(const char *filename, const char *result,
             const char *err ATTRIBUTE_UNUSED, int options ATTRIBUTE_UNUSED) {
    FILE *input, *output;
    char *temp;
    char expr[5000];
    int len;
    int ret;
    int i;
    int res = 0;
    xmlAutomataPtr am;
    xmlAutomataStatePtr states[1000];
    xmlRegexpPtr regexp = NULL;
    xmlRegExecCtxtPtr exec = NULL;

    nb_tests++;

    for (i = 0;i<1000;i++)
	states[i] = NULL;

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

    am = xmlNewAutomata();
    if (am == NULL) {
        fprintf(stderr,
		"Cannot create automata\n");
	fclose(input);
	return(-1);
    }
    states[0] = xmlAutomataGetInitState(am);
    if (states[0] == NULL) {
        fprintf(stderr,
		"Cannot get start state\n");
	xmlFreeAutomata(am);
	fclose(input);
	return(-1);
    }
    ret = 0;

    while (fgets(expr, 4500, input) != NULL) {
	if (expr[0] == '#')
	    continue;
	len = strlen(expr);
	len--;
	while ((len >= 0) &&
	       ((expr[len] == '\n') || (expr[len] == '\t') ||
		(expr[len] == '\r') || (expr[len] == ' '))) len--;
	expr[len + 1] = 0;
	if (len >= 0) {
	    if ((am != NULL) && (expr[0] == 't') && (expr[1] == ' ')) {
		char *ptr = &expr[2];
		int from, to;

		from = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		if (states[from] == NULL)
		    states[from] = xmlAutomataNewState(am);
		ptr++;
		to = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		if (states[to] == NULL)
		    states[to] = xmlAutomataNewState(am);
		ptr++;
		xmlAutomataNewTransition(am, states[from], states[to],
			                 BAD_CAST ptr, NULL);
	    } else if ((am != NULL) && (expr[0] == 'e') && (expr[1] == ' ')) {
		char *ptr = &expr[2];
		int from, to;

		from = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		if (states[from] == NULL)
		    states[from] = xmlAutomataNewState(am);
		ptr++;
		to = scanNumber(&ptr);
		if (states[to] == NULL)
		    states[to] = xmlAutomataNewState(am);
		xmlAutomataNewEpsilon(am, states[from], states[to]);
	    } else if ((am != NULL) && (expr[0] == 'f') && (expr[1] == ' ')) {
		char *ptr = &expr[2];
		int state;

		state = scanNumber(&ptr);
		if (states[state] == NULL) {
		    fprintf(stderr,
			    "Bad state %d : %s\n", state, expr);
		    break;
		}
		xmlAutomataSetFinalState(am, states[state]);
	    } else if ((am != NULL) && (expr[0] == 'c') && (expr[1] == ' ')) {
		char *ptr = &expr[2];
		int from, to;
		int min, max;

		from = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		if (states[from] == NULL)
		    states[from] = xmlAutomataNewState(am);
		ptr++;
		to = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		if (states[to] == NULL)
		    states[to] = xmlAutomataNewState(am);
		ptr++;
		min = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		ptr++;
		max = scanNumber(&ptr);
		if (*ptr != ' ') {
		    fprintf(stderr,
			    "Bad line %s\n", expr);
		    break;
		}
		ptr++;
		xmlAutomataNewCountTrans(am, states[from], states[to],
			                 BAD_CAST ptr, min, max, NULL);
	    } else if ((am != NULL) && (expr[0] == '-') && (expr[1] == '-')) {
		/* end of the automata */
		regexp = xmlAutomataCompile(am);
		xmlFreeAutomata(am);
		am = NULL;
		if (regexp == NULL) {
		    fprintf(stderr,
			    "Failed to compile the automata");
		    break;
		}
	    } else if ((expr[0] == '=') && (expr[1] == '>')) {
		if (regexp == NULL) {
		    fprintf(output, "=> failed not compiled\n");
		} else {
		    if (exec == NULL)
			exec = xmlRegNewExecCtxt(regexp, NULL, NULL);
		    if (ret == 0) {
			ret = xmlRegExecPushString(exec, NULL, NULL);
		    }
		    if (ret == 1)
			fprintf(output, "=> Passed\n");
		    else if ((ret == 0) || (ret == -1))
			fprintf(output, "=> Failed\n");
		    else if (ret < 0)
			fprintf(output, "=> Error\n");
		    xmlRegFreeExecCtxt(exec);
		    exec = NULL;
		}
		ret = 0;
	    } else if (regexp != NULL) {
		if (exec == NULL)
		    exec = xmlRegNewExecCtxt(regexp, NULL, NULL);
		ret = xmlRegExecPushString(exec, BAD_CAST expr, NULL);
	    } else {
		fprintf(stderr,
			"Unexpected line %s\n", expr);
	    }
	}
    }
    fclose(output);
    fclose(input);
    if (regexp != NULL)
	xmlRegFreeRegexp(regexp);
    if (exec != NULL)
	xmlRegFreeExecCtxt(exec);
    if (am != NULL)
	xmlFreeAutomata(am);

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

    return(res);
}

#endif /* LIBXML_REGEXP_ENABLED */

/************************************************************************
 *                                                                      *
 *                      xmlCopy Tests                                   *
 *                                                                      *
 ************************************************************************/
static int
xmlCopyEntityTest(const char *filename ATTRIBUTE_UNUSED,
                  const char *result ATTRIBUTE_UNUSED,
                  const char *err ATTRIBUTE_UNUSED,
                  int options ATTRIBUTE_UNUSED) {

    int ret = 0;
    xmlDocPtr doc = NULL;
    xmlDocPtr copy = NULL;
    xmlChar *before = NULL;
    xmlChar *after = NULL;
    const char *xml =
        "<!DOCTYPE x [<!ENTITY e \"AAAA\">]>"
        "<r ID=\"&e;_suffix\"/>";

    doc = xmlReadMemory(xml, (int)strlen(xml), "doc.xml", NULL, 0);
    if (!doc) {
        fprintf(stderr, "xmlReadMemory failed\n");
        ret = 1;
        goto done;
    }
    copy = xmlCopyDoc(doc, 1);
    if (!copy) {
        fprintf(stderr, "xmlCopyDoc failed\n");
        ret = 1;
        goto done;
    }

    before = xmlGetProp(xmlDocGetRootElement(doc), BAD_CAST "ID");
    after = xmlGetProp(xmlDocGetRootElement(copy), BAD_CAST "ID");

    if (xmlStrcmp(before, after) != 0) {
        fprintf(stderr, "RESET Attribute value changed after copy!\n");
        ret = 1;
    }

done:
    xmlFree(before);
    xmlFree(after);
    xmlFreeDoc(doc);
    xmlFreeDoc(copy);

    return ret;
}


/************************************************************************
 *									*
 *			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 },
#ifdef LIBXML_VALID_ENABLED
    { "Error cases regression tests",
      errParseTest, "./test/errors/*.xml", "result/errors/", "", ".err",
      0 },
    { "Error cases regression tests from file descriptor",
      fdParseTest, "./test/errors/*.xml", "result/errors/", "", ".err",
      0 },
    { "Error cases regression tests with entity substitution",
      errParseTest, "./test/errors/*.xml", "result/errors/", NULL, ".ent",
      XML_PARSE_NOENT },
    { "Error cases regression tests (old 1.0)",
      errParseTest, "./test/errors10/*.xml", "result/errors10/", "", ".err",
      XML_PARSE_OLD10 },
#endif
#ifdef LIBXML_READER_ENABLED
#ifdef LIBXML_VALID_ENABLED
    { "Error cases stream regression tests",
      streamParseTest, "./test/errors/*.xml", "result/errors/", NULL, ".str",
      0 },
#endif
    { "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 },
    { "XML push boundary tests" ,
      pushBoundaryTest, "./test/*", "result/", "", NULL,
      0 },
#endif
#ifdef LIBXML_HTML_ENABLED
    { "HTML regression tests" ,
      errParseTest, "./test/HTML/*", "result/HTML/", "", ".err",
      XML_PARSE_HTML },
    { "HTML regression tests from file descriptor",
      fdParseTest, "./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 },
    { "Push HTML boundary tests" ,
      pushBoundaryTest, "./test/HTML/*", "result/HTML/", "", NULL,
      XML_PARSE_HTML },
#endif
    { "HTML SAX regression tests" ,
      saxParseTest, "./test/HTML/*", "result/HTML/", ".sax", NULL,
      XML_PARSE_HTML },
#ifdef LIBXML_PUSH_ENABLED
    { "HTML tokenization tests",
      htmlTokenizerTest,
      "./test/html-tokenizer/*.test", "result/html-tokenizer/", "", NULL, 0 },
#endif
#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/", "", ".err",
      XML_PARSE_XINCLUDE },
#ifdef LIBXML_READER_ENABLED
    { "XInclude xmlReader regression tests",
      streamParseTest, "./test/XInclude/docs/*", "result/XInclude/", ".rdr",
      ".err", XML_PARSE_XINCLUDE },
#endif
    { "XInclude regression tests stripping include nodes" ,
      errParseTest, "./test/XInclude/docs/*", "result/XInclude/", "", ".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",
      ".err", XML_PARSE_XINCLUDE | XML_PARSE_NOXINCNODE },
#endif
    { "XInclude regression tests without reader",
      errParseTest, "./test/XInclude/without-reader/*", "result/XInclude/", "",
      ".err", XML_PARSE_XINCLUDE },
#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
#ifdef LIBXML_VALID_ENABLED
    { "xml:id regression tests" ,
      xmlidDocTest, "./test/xmlid/*", "result/xmlid/", "", ".err",
      0 },
#endif
#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 },
    { "Schemas XSI assembly tests" ,
      schemasXsiTest, "./test/schemas/any1_0.xml", "result/schemas/",  NULL, NULL,
      0 },
#endif
#ifdef LIBXML_RELAXNG_ENABLED
    { "Relax-NG regression tests" ,
      rngTest, "./test/relaxng/*.rng", NULL, NULL, NULL,
      XML_PARSE_DTDATTR | XML_PARSE_NOENT },
    { "Relax-NG include limit tests" ,
      rngIncludeTest, "./test/relaxng/include/include-limit.rng", NULL, NULL, NULL,
      0 },
#ifdef LIBXML_READER_ENABLED
    { "Relax-NG streaming regression tests" ,
      rngStreamTest, "./test/relaxng/*.rng", NULL, NULL, NULL,
      XML_PARSE_DTDATTR | XML_PARSE_NOENT },
#endif
#endif
#if defined(LIBXML_SCHEMATRON_ENABLED)
    { "Schematron regression tests" ,
      schematronTest, "./test/schematron/*.sct", NULL, NULL, NULL,
      0 },
#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
    { "SVG parsing regression tests" ,
      oldParseTest, "./test/SVG/*.xml", "result/SVG/", "", NULL,
      0 },
#if defined(LIBXML_REGEXP_ENABLED)
    { "Regexp regression tests" ,
      regexpTest, "./test/regexp/*", "result/regexp/", "", ".err",
      0 },
    { "Automata regression tests" ,
      automataTest, "./test/automata/*", "result/automata/", "", NULL,
      0 },
#endif

#if defined(LIBXML_READER_ENABLED) && defined(__linux__)
    { "xmlReaderForFd memory leak tests",
      xmlReaderForFdTest, "./test/slashdot.xml", NULL, NULL, NULL, 0 },
#if defined(LIBXML_OUTPUT_ENABLED) && defined(LIBXML_ZLIB_ENABLED)
    { "xmlReaderForFdGz memory leak tests",
      xmlReaderForFdGzTest, "./test/slashdot.xml", NULL, NULL, NULL, 0 },
#endif
#endif

    { "xmlCopyEntity children test" , xmlCopyEntityTest, NULL, NULL, NULL, NULL, 0 },

    {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, ibm1141Handler, eucJpHandler;

    ebcdicHandler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_EBCDIC);
    ibm1141Handler = xmlFindCharEncodingHandler("IBM-1141");

    /*
     * When decoding EUC-JP, musl doesn't seem to support 0x8F control
     * codes.
     */
    eucJpHandler = xmlGetCharEncodingHandler(XML_CHAR_ENCODING_EUC_JP);
    if (eucJpHandler != NULL) {
        xmlBufferPtr in, out;

        in = xmlBufferCreateSize(10);
        xmlBufferCCat(in, "\x8f\xe9\xae");
        out = xmlBufferCreateSize(10);
        if (xmlCharEncInFunc(eucJpHandler, out, in) != 3) {
            xmlCharEncCloseFunc(eucJpHandler);
            eucJpHandler = NULL;
        }
        xmlBufferFree(out);
        xmlBufferFree(in);
    }

    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) || (ibm1141Handler == NULL)) &&
                 (strstr(globbuf.gl_pathv[i], "ebcdic") != NULL)) ||
                ((eucJpHandler == NULL) &&
                 (strstr(globbuf.gl_pathv[i], "icu_parse_test") != NULL)))
                continue;
#if !defined(LIBXML_ICONV_ENABLED) && !defined(LIBXML_ICU_ENABLED) && \
    !defined(LIBXML_ISO8859X_ENABLED)
            if (strstr(globbuf.gl_pathv[i], "iso-8859-5") != NULL)
                continue;
#endif
	    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;
	    }
            mem = xmlMemUsed();
            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) {
                fprintf(stderr, "File %s leaked %d bytes\n",
                        globbuf.gl_pathv[i], xmlMemUsed() - mem);
                nb_leaks++;
                err++;
            }
            testErrorsSize = 0;
	    if (result)
		xmlFree(result);
	    if (error)
		xmlFree(error);
	}
	globfree(&globbuf);
    } else {
        testErrorsSize = 0;
	testErrors[0] = 0;
        res = tst->func(NULL, NULL, NULL, tst->options);
        xmlResetLastError();
	if (res != 0) {
	    nb_errors++;
	    err++;
	}
    }

    xmlCharEncCloseFunc(ebcdicHandler);
    xmlCharEncCloseFunc(ibm1141Handler);
    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)
    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 if (!strcmp(argv[a], "--out"))
	    temp_directory = argv[++a];
	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();

    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(0);
}
#endif
