code:
/*
 * runsuite.c: C program to run libxml2 againts published testsuites
 *
 * See Copyright for the status of this software.
 *
 * daniel@veillard.com
 */

#ifdef HAVE_CONFIG_H
#include "libxml.h"
#else
#include <stdio.h>
#endif

#ifdef LIBXML_XPATH_ENABLED

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

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

#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

#define LOGFILE "runxmlconf.log"
static FILE *logfile = NULL;
static int verbose = 0;

#define NB_EXPECTED_ERRORS 15

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

#define vsnprintf _vsnprintf

#define snprintf _snprintf

#endif

const char *skipped_tests[] = {
/* http://lists.w3.org/Archives/Public/public-xml-testsuite/2008Jul/0000.html */
    "rmt-ns10-035",
    NULL
};

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

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

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

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

    return(1);
}

static xmlChar *composeDir(const xmlChar *dir, const xmlChar *path) {
    char buf[500];

    if (dir == NULL) return(xmlStrdup(path));
    if (path == NULL) return(NULL);

    snprintf(buf, 500, "%s/%s", (const char *) dir, (const char *) path);
    return(xmlStrdup((const xmlChar *) buf));
}

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

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

/*
 * We need to trap calls to the resolver to not account memory for the catalog
 * and not rely on any external resources.
 */
static xmlParserInputPtr
testExternalEntityLoader(const char *URL, const char *ID ATTRIBUTE_UNUSED,
			 xmlParserCtxtPtr ctxt) {
    xmlParserInputPtr ret;

    ret = xmlNewInputFromFile(ctxt, (const char *) URL);

    return(ret);
}

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

static void test_log(const char *msg, ...) {
    va_list args;
    if (logfile != NULL) {
        fprintf(logfile, "\n------------\n");
	va_start(args, msg);
	vfprintf(logfile, msg, args);
	va_end(args);
	fprintf(logfile, "%s", testErrors);
	testErrorsSize = 0; testErrors[0] = 0;
    }
    if (verbose) {
	va_start(args, msg);
	vfprintf(stderr, msg, args);
	va_end(args);
    }
}

static void
testErrorHandler(void *userData ATTRIBUTE_UNUSED, xmlErrorPtr error) {
    int res;

    if (testErrorsSize >= 32768)
        return;
    res = snprintf(&testErrors[testErrorsSize],
                    32768 - testErrorsSize,
		   "%s:%d: %s\n", (error->file ? error->file : "entity"),
		   error->line, error->message);
    if (error->level == XML_ERR_FATAL)
        nbFatal++;
    else if (error->level == XML_ERR_ERROR)
        nbError++;
    if (testErrorsSize + res >= 32768) {
        /* buffer is full */
	testErrorsSize = 32768;
	testErrors[testErrorsSize] = 0;
    } else {
        testErrorsSize += res;
    }
    testErrors[testErrorsSize] = 0;
}

static xmlXPathContextPtr ctxtXPath;

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

    xmlMemSetup(xmlMemFree, xmlMemMalloc, xmlMemRealloc, xmlMemoryStrdup);
    xmlInitParser();
    xmlSetExternalEntityLoader(testExternalEntityLoader);
    ctxtXPath = xmlXPathNewContext(NULL);
    /*
    * Deactivate the cache if created; otherwise we have to create/free it
    * for every test, since it will confuse the memory leak detection.
    * Note that normally this need not be done, since the cache is not
    * created until set explicitely with xmlXPathContextSetCache();
    * but for test purposes it is sometimes usefull to activate the
    * cache by default for the whole library.
    */
    if (ctxtXPath->cache != NULL)
	xmlXPathContextSetCache(ctxtXPath, 0, -1, 0);
    xmlSetStructuredErrorFunc(NULL, testErrorHandler);
}

/************************************************************************
 *									*
 *		Run the xmlconf test if found				*
 *									*
 ************************************************************************/

static int
xmlconfTestInvalid(const char *id, const char *filename, int options) {
    xmlDocPtr doc;
    xmlParserCtxtPtr ctxt;
    int ret = 1;

    ctxt = xmlNewParserCtxt();
    if (ctxt == NULL) {
        test_log("test %s : %s out of memory\n",
	         id, filename);
        return(0);
    }
    doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
    if (doc == NULL) {
        test_log("test %s : %s invalid document turned not well-formed too\n",
	         id, filename);
    } else {
    /* invalidity should be reported both in the context and in the document */
        if ((ctxt->valid != 0) || (doc->properties & XML_DOC_DTDVALID)) {
	    test_log("test %s : %s failed to detect invalid document\n",
		     id, filename);
	    nb_errors++;
	    ret = 0;
	}
	xmlFreeDoc(doc);
    }
    xmlFreeParserCtxt(ctxt);
    return(ret);
}

static int
xmlconfTestValid(const char *id, const char *filename, int options) {
    xmlDocPtr doc;
    xmlParserCtxtPtr ctxt;
    int ret = 1;

    ctxt = xmlNewParserCtxt();
    if (ctxt == NULL) {
        test_log("test %s : %s out of memory\n",
	         id, filename);
        return(0);
    }
    doc = xmlCtxtReadFile(ctxt, filename, NULL, options);
    if (doc == NULL) {
        test_log("test %s : %s failed to parse a valid document\n",
	         id, filename);
        nb_errors++;
	ret = 0;
    } else {
    /* validity should be reported both in the context and in the document */
        if ((ctxt->valid == 0) || ((doc->properties & XML_DOC_DTDVALID) == 0)) {
	    test_log("test %s : %s failed to validate a valid document\n",
		     id, filename);
	    nb_errors++;
	    ret = 0;
	}
	xmlFreeDoc(doc);
    }
    xmlFreeParserCtxt(ctxt);
    return(ret);
}

static int
xmlconfTestNotNSWF(const char *id, const char *filename, int options) {
    xmlDocPtr doc;
    int ret = 1;

    /*
     * In case of Namespace errors, libxml2 will still parse the document
     * but log a Namesapce error.
     */
    doc = xmlReadFile(filename, NULL, options);
    if (doc == NULL) {
        test_log("test %s : %s failed to parse the XML\n",
	         id, filename);
        nb_errors++;
	ret = 0;
    } else {
	if ((xmlLastError.code == XML_ERR_OK) ||
	    (xmlLastError.domain != XML_FROM_NAMESPACE)) {
	    test_log("test %s : %s failed to detect namespace error\n",
		     id, filename);
	    nb_errors++;
	    ret = 0;
	}
	xmlFreeDoc(doc);
    }
    return(ret);
}

static int
xmlconfTestNotWF(const char *id, const char *filename, int options) {
    xmlDocPtr doc;
    int ret = 1;

    doc = xmlReadFile(filename, NULL, options);
    if (doc != NULL) {
        test_log("test %s : %s failed to detect not well formedness\n",
	         id, filename);
        nb_errors++;
	xmlFreeDoc(doc);
	ret = 0;
    }
    return(ret);
}

static int
xmlconfTestItem(xmlDocPtr doc, xmlNodePtr cur) {
    int ret = -1;
    xmlChar *type = NULL;
    xmlChar *filename = NULL;
    xmlChar *uri = NULL;
    xmlChar *base = NULL;
    xmlChar *id = NULL;
    xmlChar *rec = NULL;
    xmlChar *version = NULL;
    xmlChar *entities = NULL;
    xmlChar *edition = NULL;
    int options = 0;
    int nstest = 0;
    int mem, final;
    int i;

    testErrorsSize = 0; testErrors[0] = 0;
    nbError = 0;
    nbFatal = 0;
    id = xmlGetProp(cur, BAD_CAST "ID");
    if (id == NULL) {
        test_log("test missing ID, line %ld\n", xmlGetLineNo(cur));
	goto error;
    }
    for (i = 0;skipped_tests[i] != NULL;i++) {
        if (!strcmp(skipped_tests[i], (char *) id)) {
	    test_log("Skipping test %s from skipped list\n", (char *) id);
	    ret = 0;
	    nb_skipped++;
	    goto error;
	}
    }
    type = xmlGetProp(cur, BAD_CAST "TYPE");
    if (type == NULL) {
        test_log("test %s missing TYPE\n", (char *) id);
	goto error;
    }
    uri = xmlGetProp(cur, BAD_CAST "URI");
    if (uri == NULL) {
        test_log("test %s missing URI\n", (char *) id);
	goto error;
    }
    base = xmlNodeGetBase(doc, cur);
    filename = composeDir(base, uri);
    if (!checkTestFile((char *) filename)) {
        test_log("test %s missing file %s \n", id,
	         (filename ? (char *)filename : "NULL"));
	goto error;
    }

    version = xmlGetProp(cur, BAD_CAST "VERSION");

    entities = xmlGetProp(cur, BAD_CAST "ENTITIES");
    if (!xmlStrEqual(entities, BAD_CAST "none")) {
        options |= XML_PARSE_DTDLOAD;
        options |= XML_PARSE_NOENT;
    }
    rec = xmlGetProp(cur, BAD_CAST "RECOMMENDATION");
    if ((rec == NULL) ||
        (xmlStrEqual(rec, BAD_CAST "XML1.0")) ||
	(xmlStrEqual(rec, BAD_CAST "XML1.0-errata2e")) ||
	(xmlStrEqual(rec, BAD_CAST "XML1.0-errata3e")) ||
	(xmlStrEqual(rec, BAD_CAST "XML1.0-errata4e"))) {
	if ((version != NULL) && (!xmlStrEqual(version, BAD_CAST "1.0"))) {
	    test_log("Skipping test %s for %s\n", (char *) id,
	             (char *) version);
	    ret = 0;
	    nb_skipped++;
	    goto error;
	}
	ret = 1;
    } else if ((xmlStrEqual(rec, BAD_CAST "NS1.0")) ||
	       (xmlStrEqual(rec, BAD_CAST "NS1.0-errata1e"))) {
	ret = 1;
	nstest = 1;
    } else {
        test_log("Skipping test %s for REC %s\n", (char *) id, (char *) rec);
	ret = 0;
	nb_skipped++;
	goto error;
    }
    edition = xmlGetProp(cur, BAD_CAST "EDITION");
    if ((edition != NULL) && (xmlStrchr(edition, '5') == NULL)) {
        /* test limited to all versions before 5th */
	options |= XML_PARSE_OLD10;
    }

    /*
     * Reset errors and check memory usage before the test
     */
    xmlResetLastError();
    testErrorsSize = 0; testErrors[0] = 0;
    mem = xmlMemUsed();

    if (xmlStrEqual(type, BAD_CAST "not-wf")) {
        if (nstest == 0)
	    xmlconfTestNotWF((char *) id, (char *) filename, options);
        else
	    xmlconfTestNotNSWF((char *) id, (char *) filename, options);
    } else if (xmlStrEqual(type, BAD_CAST "valid")) {
        options |= XML_PARSE_DTDVALID;
	xmlconfTestValid((char *) id, (char *) filename, options);
    } else if (xmlStrEqual(type, BAD_CAST "invalid")) {
        options |= XML_PARSE_DTDVALID;
	xmlconfTestInvalid((char *) id, (char *) filename, options);
    } else if (xmlStrEqual(type, BAD_CAST "error")) {
        test_log("Skipping error test %s \n", (char *) id);
	ret = 0;
	nb_skipped++;
	goto error;
    } else {
        test_log("test %s unknown TYPE value %s\n", (char *) id, (char *)type);
	ret = -1;
	goto error;
    }

    /*
     * Reset errors and check memory usage after the test
     */
    xmlResetLastError();
    final = xmlMemUsed();
    if (final > mem) {
        test_log("test %s : %s leaked %d bytes\n",
	         id, filename, final - mem);
        nb_leaks++;
	xmlMemDisplayLast(logfile, final - mem);
    }
    nb_tests++;

error:
    if (type != NULL)
        xmlFree(type);
    if (entities != NULL)
        xmlFree(entities);
    if (edition != NULL)
        xmlFree(edition);
    if (version != NULL)
        xmlFree(version);
    if (filename != NULL)
        xmlFree(filename);
    if (uri != NULL)
        xmlFree(uri);
    if (base != NULL)
        xmlFree(base);
    if (id != NULL)
        xmlFree(id);
    if (rec != NULL)
        xmlFree(rec);
    return(ret);
}

static int
xmlconfTestCases(xmlDocPtr doc, xmlNodePtr cur, int level) {
    xmlChar *profile;
    int ret = 0;
    int tests = 0;
    int output = 0;

    if (level == 1) {
	profile = xmlGetProp(cur, BAD_CAST "PROFILE");
	if (profile != NULL) {
	    output = 1;
	    level++;
	    printf("Test cases: %s\n", (char *) profile);
	    xmlFree(profile);
	}
    }
    cur = cur->children;
    while (cur != NULL) {
        /* look only at elements we ignore everything else */
        if (cur->type == XML_ELEMENT_NODE) {
	    if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) {
	        ret += xmlconfTestCases(doc, cur, level);
	    } else if (xmlStrEqual(cur->name, BAD_CAST "TEST")) {
	        if (xmlconfTestItem(doc, cur) >= 0)
		    ret++;
		tests++;
	    } else {
	        fprintf(stderr, "Unhandled element %s\n", (char *)cur->name);
	    }
	}
        cur = cur->next;
    }
    if (output == 1) {
	if (tests > 0)
	    printf("Test cases: %d tests\n", tests);
    }
    return(ret);
}

static int
xmlconfTestSuite(xmlDocPtr doc, xmlNodePtr cur) {
    xmlChar *profile;
    int ret = 0;

    profile = xmlGetProp(cur, BAD_CAST "PROFILE");
    if (profile != NULL) {
        printf("Test suite: %s\n", (char *) profile);
	xmlFree(profile);
    } else
        printf("Test suite\n");
    cur = cur->children;
    while (cur != NULL) {
        /* look only at elements we ignore everything else */
        if (cur->type == XML_ELEMENT_NODE) {
	    if (xmlStrEqual(cur->name, BAD_CAST "TESTCASES")) {
	        ret += xmlconfTestCases(doc, cur, 1);
	    } else {
	        fprintf(stderr, "Unhandled element %s\n", (char *)cur->name);
	    }
	}
        cur = cur->next;
    }
    return(ret);
}

static void
xmlconfInfo(void) {
    fprintf(stderr, "  you need to fetch and extract the\n");
    fprintf(stderr, "  latest XML Conformance Test Suites\n");
    fprintf(stderr, "  http://www.w3.org/XML/Test/xmlts20080827.tar.gz\n");
    fprintf(stderr, "  see http://www.w3.org/XML/Test/ for informations\n");
}

static int
xmlconfTest(void) {
    const char *confxml = "xmlconf/xmlconf.xml";
    xmlDocPtr doc;
    xmlNodePtr cur;
    int ret = 0;

    if (!checkTestFile(confxml)) {
        fprintf(stderr, "%s is missing \n", confxml);
	xmlconfInfo();
	return(-1);
    }
    doc = xmlReadFile(confxml, NULL, XML_PARSE_NOENT);
    if (doc == NULL) {
        fprintf(stderr, "%s is corrupted \n", confxml);
	xmlconfInfo();
	return(-1);
    }

    cur = xmlDocGetRootElement(doc);
    if ((cur == NULL) || (!xmlStrEqual(cur->name, BAD_CAST "TESTSUITE"))) {
        fprintf(stderr, "Unexpected format %s\n", confxml);
	xmlconfInfo();
	ret = -1;
    } else {
        ret = xmlconfTestSuite(doc, cur);
    }
    xmlFreeDoc(doc);
    return(ret);
}

/************************************************************************
 *									*
 *		The driver for the tests				*
 *									*
 ************************************************************************/

int
main(int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED) {
    int ret = 0;
    int old_errors, old_tests, old_leaks;

    logfile = fopen(LOGFILE, "w");
    if (logfile == NULL) {
        fprintf(stderr,
	        "Could not open the log file, running in verbose mode\n");
	verbose = 1;
    }
    initializeLibxml2();

    if ((argc >= 2) && (!strcmp(argv[1], "-v")))
        verbose = 1;

    old_errors = nb_errors;
    old_tests = nb_tests;
    old_leaks = nb_leaks;
    xmlconfTest();
    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);
    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);
	printf("See %s for detailed output\n", LOGFILE);
	if ((nb_leaks == 0) && (nb_errors == NB_EXPECTED_ERRORS)) {
	    printf("%d errors were expected\n", nb_errors);
	    ret = 0;
	}
    }
    xmlXPathFreeContext(ctxtXPath);
    xmlCleanupParser();
    xmlMemoryDump();

    if (logfile != NULL)
        fclose(logfile);
    return(ret);
}

#else /* ! LIBXML_XPATH_ENABLED */
#include <stdio.h>
int
main(int argc, char **argv) {
    fprintf(stderr, "%s need XPath support\n", argv[0]);
}
#endif

license: mit


code:
/*
 * schematron.c : implementation of the Schematron schema validity checking
 *
 * See Copyright for the status of this software.
 *
 * Daniel Veillard <daniel@veillard.com>
 */

/*
 * TODO:
 * + double check the semantic, especially
 *        - multiple rules applying in a single pattern/node
 *        - the semantic of libxml2 patterns vs. XSLT production referenced
 *          by the spec.
 * + export of results in SVRL
 * + full parsing and coverage of the spec, conformance of the input to the
 *   spec
 * + divergences between the draft and the ISO proposed standard :-(
 * + hook and test include
 * + try and compare with the XSLT version
 */

#define IN_LIBXML
#include "libxml.h"

#ifdef LIBXML_SCHEMATRON_ENABLED

#include <string.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/uri.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/pattern.h>
#include <libxml/schematron.h>

#define SCHEMATRON_PARSE_OPTIONS XML_PARSE_NOENT

#define SCT_OLD_NS BAD_CAST "http://www.ascc.net/xml/schematron"

#define XML_SCHEMATRON_NS BAD_CAST "http://purl.oclc.org/dsdl/schematron"

static const xmlChar *xmlSchematronNs = XML_SCHEMATRON_NS;
static const xmlChar *xmlOldSchematronNs = SCT_OLD_NS;

#define IS_SCHEMATRON(node, elem)					\
   ((node != NULL) && (node->type == XML_ELEMENT_NODE ) &&		\
    (node->ns != NULL) &&						\
    (xmlStrEqual(node->name, (const xmlChar *) elem)) &&		\
    ((xmlStrEqual(node->ns->href, xmlSchematronNs)) ||			\
     (xmlStrEqual(node->ns->href, xmlOldSchematronNs))))

#define NEXT_SCHEMATRON(node)						\
   while (node != NULL) {						\
       if ((node->type == XML_ELEMENT_NODE ) && (node->ns != NULL) && 	\
           ((xmlStrEqual(node->ns->href, xmlSchematronNs)) ||		\
	    (xmlStrEqual(node->ns->href, xmlOldSchematronNs))))		\
	   break;							\
       node = node->next;						\
   }

/**
 * TODO:
 *
 * macro to flag unimplemented blocks
 */
#define TODO 								\
    xmlGenericError(xmlGenericErrorContext,				\
	    "Unimplemented block at %s:%d\n",				\
            __FILE__, __LINE__);

typedef enum {
    XML_SCHEMATRON_ASSERT=1,
    XML_SCHEMATRON_REPORT=2
} xmlSchematronTestType;

/**
 * _xmlSchematronTest:
 *
 * A Schematrons test, either an assert or a report
 */
typedef struct _xmlSchematronTest xmlSchematronTest;
typedef xmlSchematronTest *xmlSchematronTestPtr;
struct _xmlSchematronTest {
    xmlSchematronTestPtr next;	/* the next test in the list */
    xmlSchematronTestType type;	/* the test type */
    xmlNodePtr node;		/* the node in the tree */
    xmlChar *test;		/* the expression to test */
    xmlXPathCompExprPtr comp;	/* the compiled expression */
    xmlChar *report;		/* the message to report */
};

/**
 * _xmlSchematronRule:
 *
 * A Schematrons rule
 */
typedef struct _xmlSchematronRule xmlSchematronRule;
typedef xmlSchematronRule *xmlSchematronRulePtr;
struct _xmlSchematronRule {
    xmlSchematronRulePtr next;	/* the next rule in the list */
    xmlSchematronRulePtr patnext;/* the next rule in the pattern list */
    xmlNodePtr node;		/* the node in the tree */
    xmlChar *context;		/* the context evaluation rule */
    xmlSchematronTestPtr tests;	/* the list of tests */
    xmlPatternPtr pattern;	/* the compiled pattern associated */
    xmlChar *report;		/* the message to report */
};

/**
 * _xmlSchematronPattern:
 *
 * A Schematrons pattern
 */
typedef struct _xmlSchematronPattern xmlSchematronPattern;
typedef xmlSchematronPattern *xmlSchematronPatternPtr;
struct _xmlSchematronPattern {
    xmlSchematronPatternPtr next;/* the next pattern in the list */
    xmlSchematronRulePtr rules;	/* the list of rules */
    xmlChar *name;		/* the name of the pattern */
};

/**
 * _xmlSchematron:
 *
 * A Schematrons definition
 */
struct _xmlSchematron {
    const xmlChar *name;	/* schema name */
    int preserve;		/* was the document passed by the user */
    xmlDocPtr doc;		/* pointer to the parsed document */
    int flags;			/* specific to this schematron */

    void *_private;		/* unused by the library */
    xmlDictPtr dict;		/* the dictionnary used internally */

    const xmlChar *title;	/* the title if any */

    int nbNs;			/* the number of namespaces */

    int nbPattern;		/* the number of patterns */
    xmlSchematronPatternPtr patterns;/* the patterns found */
    xmlSchematronRulePtr rules;	/* the rules gathered */
    int nbNamespaces;		/* number of namespaces in the array */
    int maxNamespaces;		/* size of the array */
    const xmlChar **namespaces;	/* the array of namespaces */
};

/**
 * xmlSchematronValidCtxt:
 *
 * A Schematrons validation context
 */
struct _xmlSchematronValidCtxt {
    int type;
    int flags;			/* an or of xmlSchematronValidOptions */

    xmlDictPtr dict;
    int nberrors;
    int err;

    xmlSchematronPtr schema;
    xmlXPathContextPtr xctxt;

    FILE *outputFile;		/* if using XML_SCHEMATRON_OUT_FILE */
    xmlBufferPtr outputBuffer;	/* if using XML_SCHEMATRON_OUT_BUFFER */
    xmlOutputWriteCallback iowrite; /* if using XML_SCHEMATRON_OUT_IO */
    xmlOutputCloseCallback  ioclose;
    void *ioctx;

    /* error reporting data */
    void *userData;                      /* user specific data block */
    xmlSchematronValidityErrorFunc error;/* the callback in case of errors */
    xmlSchematronValidityWarningFunc warning;/* callback in case of warning */
    xmlStructuredErrorFunc serror;       /* the structured function */
};

struct _xmlSchematronParserCtxt {
    int type;
    const xmlChar *URL;
    xmlDocPtr doc;
    int preserve;               /* Whether the doc should be freed  */
    const char *buffer;
    int size;

    xmlDictPtr dict;            /* dictionnary for interned string names */

    int nberrors;
    int err;
    xmlXPathContextPtr xctxt;	/* the XPath context used for compilation */
    xmlSchematronPtr schema;

    int nbNamespaces;		/* number of namespaces in the array */
    int maxNamespaces;		/* size of the array */
    const xmlChar **namespaces;	/* the array of namespaces */

    int nbIncludes;		/* number of includes in the array */
    int maxIncludes;		/* size of the array */
    xmlNodePtr *includes;	/* the array of includes */

    /* error reporting data */
    void *userData;                      /* user specific data block */
    xmlSchematronValidityErrorFunc error;/* the callback in case of errors */
    xmlSchematronValidityWarningFunc warning;/* callback in case of warning */
    xmlStructuredErrorFunc serror;       /* the structured function */
};

#define XML_STRON_CTXT_PARSER 1
#define XML_STRON_CTXT_VALIDATOR 2

/************************************************************************
 *									*
 *			Error reporting					*
 *									*
 ************************************************************************/

/**
 * xmlSchematronPErrMemory:
 * @node: a context node
 * @extra:  extra informations
 *
 * Handle an out of memory condition
 */
static void
xmlSchematronPErrMemory(xmlSchematronParserCtxtPtr ctxt,
                        const char *extra, xmlNodePtr node)
{
    if (ctxt != NULL)
        ctxt->nberrors++;
    __xmlSimpleError(XML_FROM_SCHEMASP, XML_ERR_NO_MEMORY, node, NULL,
                     extra);
}

/**
 * xmlSchematronPErr:
 * @ctxt: the parsing context
 * @node: the context node
 * @error: the error code
 * @msg: the error message
 * @str1: extra data
 * @str2: extra data
 *
 * Handle a parser error
 */
static void
xmlSchematronPErr(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr node, int error,
              const char *msg, const xmlChar * str1, const xmlChar * str2)
{
    xmlGenericErrorFunc channel = NULL;
    xmlStructuredErrorFunc schannel = NULL;
    void *data = NULL;

    if (ctxt != NULL) {
        ctxt->nberrors++;
        channel = ctxt->error;
        data = ctxt->userData;
	schannel = ctxt->serror;
    }
    __xmlRaiseError(schannel, channel, data, ctxt, node, XML_FROM_SCHEMASP,
                    error, XML_ERR_ERROR, NULL, 0,
                    (const char *) str1, (const char *) str2, NULL, 0, 0,
                    msg, str1, str2);
}

/**
 * xmlSchematronVTypeErrMemory:
 * @node: a context node
 * @extra:  extra informations
 *
 * Handle an out of memory condition
 */
static void
xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt,
                        const char *extra, xmlNodePtr node)
{
    if (ctxt != NULL) {
        ctxt->nberrors++;
        ctxt->err = XML_SCHEMAV_INTERNAL;
    }
    __xmlSimpleError(XML_FROM_SCHEMASV, XML_ERR_NO_MEMORY, node, NULL,
                     extra);
}

/************************************************************************
 *									*
 *		Parsing and compilation of the Schematrontrons		*
 *									*
 ************************************************************************/

/**
 * xmlSchematronAddTest:
 * @ctxt: the schema parsing context
 * @type:  the type of test
 * @rule:  the parent rule
 * @node:  the node hosting the test
 * @test: the associated test
 * @report: the associated report string
 *
 * Add a test to a schematron
 *
 * Returns the new pointer or NULL in case of error
 */
static xmlSchematronTestPtr
xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt,
                     xmlSchematronTestType type,
                     xmlSchematronRulePtr rule,
                     xmlNodePtr node, xmlChar *test, xmlChar *report)
{
    xmlSchematronTestPtr ret;
    xmlXPathCompExprPtr comp;

    if ((ctxt == NULL) || (rule == NULL) || (node == NULL) ||
        (test == NULL))
        return(NULL);

    /*
     * try first to compile the test expression
     */
    comp = xmlXPathCtxtCompile(ctxt->xctxt, test);
    if (comp == NULL) {
	xmlSchematronPErr(ctxt, node,
	    XML_SCHEMAP_NOROOT,
	    "Failed to compile test expression %s",
	    test, NULL);
	return(NULL);
    }

    ret = (xmlSchematronTestPtr) xmlMalloc(sizeof(xmlSchematronTest));
    if (ret == NULL) {
        xmlSchematronPErrMemory(ctxt, "allocating schema test", node);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronTest));
    ret->type = type;
    ret->node = node;
    ret->test = test;
    ret->comp = comp;
    ret->report = report;
    ret->next = NULL;
    if (rule->tests == NULL) {
	rule->tests = ret;
    } else {
        xmlSchematronTestPtr prev = rule->tests;

	while (prev->next != NULL)
	     prev = prev->next;
        prev->next = ret;
    }
    return (ret);
}

/**
 * xmlSchematronFreeTests:
 * @tests:  a list of tests
 *
 * Free a list of tests.
 */
static void
xmlSchematronFreeTests(xmlSchematronTestPtr tests) {
    xmlSchematronTestPtr next;

    while (tests != NULL) {
        next = tests->next;
	if (tests->test != NULL)
	    xmlFree(tests->test);
	if (tests->comp != NULL)
	    xmlXPathFreeCompExpr(tests->comp);
	if (tests->report != NULL)
	    xmlFree(tests->report);
	xmlFree(tests);
	tests = next;
    }
}

/**
 * xmlSchematronAddRule:
 * @ctxt: the schema parsing context
 * @schema:  a schema structure
 * @node:  the node hosting the rule
 * @context: the associated context string
 * @report: the associated report string
 *
 * Add a rule to a schematron
 *
 * Returns the new pointer or NULL in case of error
 */
static xmlSchematronRulePtr
xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt, xmlSchematronPtr schema,
                     xmlSchematronPatternPtr pat, xmlNodePtr node,
		     xmlChar *context, xmlChar *report)
{
    xmlSchematronRulePtr ret;
    xmlPatternPtr pattern;

    if ((ctxt == NULL) || (schema == NULL) || (node == NULL) ||
        (context == NULL))
        return(NULL);

    /*
     * Try first to compile the pattern
     */
    pattern = xmlPatterncompile(context, ctxt->dict, XML_PATTERN_XPATH,
                                ctxt->namespaces);
    if (pattern == NULL) {
	xmlSchematronPErr(ctxt, node,
	    XML_SCHEMAP_NOROOT,
	    "Failed to compile context expression %s",
	    context, NULL);
    }

    ret = (xmlSchematronRulePtr) xmlMalloc(sizeof(xmlSchematronRule));
    if (ret == NULL) {
        xmlSchematronPErrMemory(ctxt, "allocating schema rule", node);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronRule));
    ret->node = node;
    ret->context = context;
    ret->pattern = pattern;
    ret->report = report;
    ret->next = NULL;
    if (schema->rules == NULL) {
	schema->rules = ret;
    } else {
        xmlSchematronRulePtr prev = schema->rules;

	while (prev->next != NULL)
	     prev = prev->next;
        prev->next = ret;
    }
    ret->patnext = NULL;
    if (pat->rules == NULL) {
	pat->rules = ret;
    } else {
        xmlSchematronRulePtr prev = pat->rules;

	while (prev->patnext != NULL)
	     prev = prev->patnext;
        prev->patnext = ret;
    }
    return (ret);
}

/**
 * xmlSchematronFreeRules:
 * @rules:  a list of rules
 *
 * Free a list of rules.
 */
static void
xmlSchematronFreeRules(xmlSchematronRulePtr rules) {
    xmlSchematronRulePtr next;

    while (rules != NULL) {
        next = rules->next;
	if (rules->tests)
	    xmlSchematronFreeTests(rules->tests);
	if (rules->context != NULL)
	    xmlFree(rules->context);
	if (rules->pattern)
	    xmlFreePattern(rules->pattern);
	if (rules->report != NULL)
	    xmlFree(rules->report);
	xmlFree(rules);
	rules = next;
    }
}

/**
 * xmlSchematronAddPattern:
 * @ctxt: the schema parsing context
 * @schema:  a schema structure
 * @node:  the node hosting the pattern
 * @id: the id or name of the pattern
 *
 * Add a pattern to a schematron
 *
 * Returns the new pointer or NULL in case of error
 */
static xmlSchematronPatternPtr
xmlSchematronAddPattern(xmlSchematronParserCtxtPtr ctxt,
                     xmlSchematronPtr schema, xmlNodePtr node, xmlChar *name)
{
    xmlSchematronPatternPtr ret;

    if ((ctxt == NULL) || (schema == NULL) || (node == NULL) || (name == NULL))
        return(NULL);

    ret = (xmlSchematronPatternPtr) xmlMalloc(sizeof(xmlSchematronPattern));
    if (ret == NULL) {
        xmlSchematronPErrMemory(ctxt, "allocating schema pattern", node);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronPattern));
    ret->name = name;
    ret->next = NULL;
    if (schema->patterns == NULL) {
	schema->patterns = ret;
    } else {
        xmlSchematronPatternPtr prev = schema->patterns;

	while (prev->next != NULL)
	     prev = prev->next;
        prev->next = ret;
    }
    return (ret);
}

/**
 * xmlSchematronFreePatterns:
 * @patterns:  a list of patterns
 *
 * Free a list of patterns.
 */
static void
xmlSchematronFreePatterns(xmlSchematronPatternPtr patterns) {
    xmlSchematronPatternPtr next;

    while (patterns != NULL) {
        next = patterns->next;
	if (patterns->name != NULL)
	    xmlFree(patterns->name);
	xmlFree(patterns);
	patterns = next;
    }
}

/**
 * xmlSchematronNewSchematron:
 * @ctxt:  a schema validation context
 *
 * Allocate a new Schematron structure.
 *
 * Returns the newly allocated structure or NULL in case or error
 */
static xmlSchematronPtr
xmlSchematronNewSchematron(xmlSchematronParserCtxtPtr ctxt)
{
    xmlSchematronPtr ret;

    ret = (xmlSchematronPtr) xmlMalloc(sizeof(xmlSchematron));
    if (ret == NULL) {
        xmlSchematronPErrMemory(ctxt, "allocating schema", NULL);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematron));
    ret->dict = ctxt->dict;
    xmlDictReference(ret->dict);

    return (ret);
}

/**
 * xmlSchematronFree:
 * @schema:  a schema structure
 *
 * Deallocate a Schematron structure.
 */
void
xmlSchematronFree(xmlSchematronPtr schema)
{
    if (schema == NULL)
        return;

    if ((schema->doc != NULL) && (!(schema->preserve)))
        xmlFreeDoc(schema->doc);

    if (schema->namespaces != NULL)
        xmlFree((char **) schema->namespaces);

    xmlSchematronFreeRules(schema->rules);
    xmlSchematronFreePatterns(schema->patterns);
    xmlDictFree(schema->dict);
    xmlFree(schema);
}

/**
 * xmlSchematronNewParserCtxt:
 * @URL:  the location of the schema
 *
 * Create an XML Schematrons parse context for that file/resource expected
 * to contain an XML Schematrons file.
 *
 * Returns the parser context or NULL in case of error
 */
xmlSchematronParserCtxtPtr
xmlSchematronNewParserCtxt(const char *URL)
{
    xmlSchematronParserCtxtPtr ret;

    if (URL == NULL)
        return (NULL);

    ret =
        (xmlSchematronParserCtxtPtr)
        xmlMalloc(sizeof(xmlSchematronParserCtxt));
    if (ret == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser context",
                                NULL);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronParserCtxt));
    ret->type = XML_STRON_CTXT_PARSER;
    ret->dict = xmlDictCreate();
    ret->URL = xmlDictLookup(ret->dict, (const xmlChar *) URL, -1);
    ret->includes = NULL;
    ret->xctxt = xmlXPathNewContext(NULL);
    if (ret->xctxt == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
                                NULL);
	xmlSchematronFreeParserCtxt(ret);
        return (NULL);
    }
    ret->xctxt->flags = XML_XPATH_CHECKNS;
    return (ret);
}

/**
 * xmlSchematronNewMemParserCtxt:
 * @buffer:  a pointer to a char array containing the schemas
 * @size:  the size of the array
 *
 * Create an XML Schematrons parse context for that memory buffer expected
 * to contain an XML Schematrons file.
 *
 * Returns the parser context or NULL in case of error
 */
xmlSchematronParserCtxtPtr
xmlSchematronNewMemParserCtxt(const char *buffer, int size)
{
    xmlSchematronParserCtxtPtr ret;

    if ((buffer == NULL) || (size <= 0))
        return (NULL);

    ret =
        (xmlSchematronParserCtxtPtr)
        xmlMalloc(sizeof(xmlSchematronParserCtxt));
    if (ret == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser context",
                                NULL);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronParserCtxt));
    ret->buffer = buffer;
    ret->size = size;
    ret->dict = xmlDictCreate();
    ret->xctxt = xmlXPathNewContext(NULL);
    if (ret->xctxt == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
                                NULL);
	xmlSchematronFreeParserCtxt(ret);
        return (NULL);
    }
    return (ret);
}

/**
 * xmlSchematronNewDocParserCtxt:
 * @doc:  a preparsed document tree
 *
 * Create an XML Schematrons parse context for that document.
 * NB. The document may be modified during the parsing process.
 *
 * Returns the parser context or NULL in case of error
 */
xmlSchematronParserCtxtPtr
xmlSchematronNewDocParserCtxt(xmlDocPtr doc)
{
    xmlSchematronParserCtxtPtr ret;

    if (doc == NULL)
        return (NULL);

    ret =
        (xmlSchematronParserCtxtPtr)
        xmlMalloc(sizeof(xmlSchematronParserCtxt));
    if (ret == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser context",
                                NULL);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronParserCtxt));
    ret->doc = doc;
    ret->dict = xmlDictCreate();
    /* The application has responsibility for the document */
    ret->preserve = 1;
    ret->xctxt = xmlXPathNewContext(doc);
    if (ret->xctxt == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
                                NULL);
	xmlSchematronFreeParserCtxt(ret);
        return (NULL);
    }

    return (ret);
}

/**
 * xmlSchematronFreeParserCtxt:
 * @ctxt:  the schema parser context
 *
 * Free the resources associated to the schema parser context
 */
void
xmlSchematronFreeParserCtxt(xmlSchematronParserCtxtPtr ctxt)
{
    if (ctxt == NULL)
        return;
    if (ctxt->doc != NULL && !ctxt->preserve)
        xmlFreeDoc(ctxt->doc);
    if (ctxt->xctxt != NULL) {
        xmlXPathFreeContext(ctxt->xctxt);
    }
    if (ctxt->namespaces != NULL)
        xmlFree((char **) ctxt->namespaces);
    xmlDictFree(ctxt->dict);
    xmlFree(ctxt);
}

#if 0
/**
 * xmlSchematronPushInclude:
 * @ctxt:  the schema parser context
 * @doc:  the included document
 * @cur:  the current include node
 *
 * Add an included document
 */
static void
xmlSchematronPushInclude(xmlSchematronParserCtxtPtr ctxt,
                        xmlDocPtr doc, xmlNodePtr cur)
{
    if (ctxt->includes == NULL) {
        ctxt->maxIncludes = 10;
        ctxt->includes = (xmlNodePtr *)
	    xmlMalloc(ctxt->maxIncludes * 2 * sizeof(xmlNodePtr));
	if (ctxt->includes == NULL) {
	    xmlSchematronPErrMemory(NULL, "allocating parser includes",
				    NULL);
	    return;
	}
        ctxt->nbIncludes = 0;
    } else if (ctxt->nbIncludes + 2 >= ctxt->maxIncludes) {
        xmlNodePtr *tmp;

	tmp = (xmlNodePtr *)
	    xmlRealloc(ctxt->includes, ctxt->maxIncludes * 4 *
	               sizeof(xmlNodePtr));
	if (tmp == NULL) {
	    xmlSchematronPErrMemory(NULL, "allocating parser includes",
				    NULL);
	    return;
	}
        ctxt->includes = tmp;
	ctxt->maxIncludes *= 2;
    }
    ctxt->includes[2 * ctxt->nbIncludes] = cur;
    ctxt->includes[2 * ctxt->nbIncludes + 1] = (xmlNodePtr) doc;
    ctxt->nbIncludes++;
}

/**
 * xmlSchematronPopInclude:
 * @ctxt:  the schema parser context
 *
 * Pop an include level. The included document is being freed
 *
 * Returns the node immediately following the include or NULL if the
 *         include list was empty.
 */
static xmlNodePtr
xmlSchematronPopInclude(xmlSchematronParserCtxtPtr ctxt)
{
    xmlDocPtr doc;
    xmlNodePtr ret;

    if (ctxt->nbIncludes <= 0)
        return(NULL);
    ctxt->nbIncludes--;
    doc = (xmlDocPtr) ctxt->includes[2 * ctxt->nbIncludes + 1];
    ret = ctxt->includes[2 * ctxt->nbIncludes];
    xmlFreeDoc(doc);
    if (ret != NULL)
	ret = ret->next;
    if (ret == NULL)
        return(xmlSchematronPopInclude(ctxt));
    return(ret);
}
#endif

/**
 * xmlSchematronAddNamespace:
 * @ctxt:  the schema parser context
 * @prefix:  the namespace prefix
 * @ns:  the namespace name
 *
 * Add a namespace definition in the context
 */
static void
xmlSchematronAddNamespace(xmlSchematronParserCtxtPtr ctxt,
                          const xmlChar *prefix, const xmlChar *ns)
{
    if (ctxt->namespaces == NULL) {
        ctxt->maxNamespaces = 10;
        ctxt->namespaces = (const xmlChar **)
	    xmlMalloc(ctxt->maxNamespaces * 2 * sizeof(const xmlChar *));
	if (ctxt->namespaces == NULL) {
	    xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
				    NULL);
	    return;
	}
        ctxt->nbNamespaces = 0;
    } else if (ctxt->nbNamespaces + 2 >= ctxt->maxNamespaces) {
        const xmlChar **tmp;

	tmp = (const xmlChar **)
	    xmlRealloc((xmlChar **) ctxt->namespaces, ctxt->maxNamespaces * 4 *
	               sizeof(const xmlChar *));
	if (tmp == NULL) {
	    xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
				    NULL);
	    return;
	}
        ctxt->namespaces = tmp;
	ctxt->maxNamespaces *= 2;
    }
    ctxt->namespaces[2 * ctxt->nbNamespaces] =
        xmlDictLookup(ctxt->dict, ns, -1);
    ctxt->namespaces[2 * ctxt->nbNamespaces + 1] =
        xmlDictLookup(ctxt->dict, prefix, -1);
    ctxt->nbNamespaces++;
    ctxt->namespaces[2 * ctxt->nbNamespaces] = NULL;
    ctxt->namespaces[2 * ctxt->nbNamespaces + 1] = NULL;

}

/**
 * xmlSchematronParseRule:
 * @ctxt:  a schema validation context
 * @rule:  the rule node
 *
 * parse a rule element
 */
static void
xmlSchematronParseRule(xmlSchematronParserCtxtPtr ctxt,
                       xmlSchematronPatternPtr pattern,
		       xmlNodePtr rule)
{
    xmlNodePtr cur;
    int nbChecks = 0;
    xmlChar *test;
    xmlChar *context;
    xmlChar *report;
    xmlSchematronRulePtr ruleptr;
    xmlSchematronTestPtr testptr;

    if ((ctxt == NULL) || (rule == NULL)) return;

    context = xmlGetNoNsProp(rule, BAD_CAST "context");
    if (context == NULL) {
	xmlSchematronPErr(ctxt, rule,
	    XML_SCHEMAP_NOROOT,
	    "rule has no context attribute",
	    NULL, NULL);
	return;
    } else if (context[0] == 0) {
	xmlSchematronPErr(ctxt, rule,
	    XML_SCHEMAP_NOROOT,
	    "rule has an empty context attribute",
	    NULL, NULL);
	xmlFree(context);
	return;
    } else {
	ruleptr = xmlSchematronAddRule(ctxt, ctxt->schema, pattern,
	                               rule, context, NULL);
	if (ruleptr == NULL) {
	    xmlFree(context);
	    return;
	}
    }

    cur = rule->children;
    NEXT_SCHEMATRON(cur);
    while (cur != NULL) {
	if (IS_SCHEMATRON(cur, "assert")) {
	    nbChecks++;
	    test = xmlGetNoNsProp(cur, BAD_CAST "test");
	    if (test == NULL) {
		xmlSchematronPErr(ctxt, cur,
		    XML_SCHEMAP_NOROOT,
		    "assert has no test attribute",
		    NULL, NULL);
	    } else if (test[0] == 0) {
		xmlSchematronPErr(ctxt, cur,
		    XML_SCHEMAP_NOROOT,
		    "assert has an empty test attribute",
		    NULL, NULL);
		xmlFree(test);
	    } else {
		/* TODO will need dynamic processing instead */
		report = xmlNodeGetContent(cur);

		testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_ASSERT,
		                               ruleptr, cur, test, report);
		if (testptr == NULL)
		    xmlFree(test);
	    }
	} else if (IS_SCHEMATRON(cur, "report")) {
	    nbChecks++;
	    test = xmlGetNoNsProp(cur, BAD_CAST "test");
	    if (test == NULL) {
		xmlSchematronPErr(ctxt, cur,
		    XML_SCHEMAP_NOROOT,
		    "assert has no test attribute",
		    NULL, NULL);
	    } else if (test[0] == 0) {
		xmlSchematronPErr(ctxt, cur,
		    XML_SCHEMAP_NOROOT,
		    "assert has an empty test attribute",
		    NULL, NULL);
		xmlFree(test);
	    } else {
		/* TODO will need dynamic processing instead */
		report = xmlNodeGetContent(cur);

		testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_REPORT,
		                               ruleptr, cur, test, report);
		if (testptr == NULL)
		    xmlFree(test);
	    }
	} else {
	    xmlSchematronPErr(ctxt, cur,
		XML_SCHEMAP_NOROOT,
		"Expecting an assert or a report element instead of %s",
		cur->name, NULL);
	}
	cur = cur->next;
	NEXT_SCHEMATRON(cur);
    }
    if (nbChecks == 0) {
	xmlSchematronPErr(ctxt, rule,
	    XML_SCHEMAP_NOROOT,
	    "rule has no assert nor report element", NULL, NULL);
    }
}

/**
 * xmlSchematronParsePattern:
 * @ctxt:  a schema validation context
 * @pat:  the pattern node
 *
 * parse a pattern element
 */
static void
xmlSchematronParsePattern(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr pat)
{
    xmlNodePtr cur;
    xmlSchematronPatternPtr pattern;
    int nbRules = 0;
    xmlChar *id;

    if ((ctxt == NULL) || (pat == NULL)) return;

    id = xmlGetNoNsProp(pat, BAD_CAST "id");
    if (id == NULL) {
	id = xmlGetNoNsProp(pat, BAD_CAST "name");
    }
    pattern = xmlSchematronAddPattern(ctxt, ctxt->schema, pat, id);
    if (pattern == NULL) {
	if (id != NULL)
	    xmlFree(id);
        return;
    }
    cur = pat->children;
    NEXT_SCHEMATRON(cur);
    while (cur != NULL) {
	if (IS_SCHEMATRON(cur, "rule")) {
	    xmlSchematronParseRule(ctxt, pattern, cur);
	    nbRules++;
	} else {
	    xmlSchematronPErr(ctxt, cur,
		XML_SCHEMAP_NOROOT,
		"Expecting a rule element instead of %s", cur->name, NULL);
	}
	cur = cur->next;
	NEXT_SCHEMATRON(cur);
    }
    if (nbRules == 0) {
	xmlSchematronPErr(ctxt, pat,
	    XML_SCHEMAP_NOROOT,
	    "Pattern has no rule element", NULL, NULL);
    }
}

#if 0
/**
 * xmlSchematronLoadInclude:
 * @ctxt:  a schema validation context
 * @cur:  the include element
 *
 * Load the include document, Push the current pointer
 *
 * Returns the updated node pointer
 */
static xmlNodePtr
xmlSchematronLoadInclude(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr cur)
{
    xmlNodePtr ret = NULL;
    xmlDocPtr doc = NULL;
    xmlChar *href = NULL;
    xmlChar *base = NULL;
    xmlChar *URI = NULL;

    if ((ctxt == NULL) || (cur == NULL))
        return(NULL);

    href = xmlGetNoNsProp(cur, BAD_CAST "href");
    if (href == NULL) {
	xmlSchematronPErr(ctxt, cur,
	    XML_SCHEMAP_NOROOT,
	    "Include has no href attribute", NULL, NULL);
	return(cur->next);
    }

    /* do the URI base composition, load and find the root */
    base = xmlNodeGetBase(cur->doc, cur);
    URI = xmlBuildURI(href, base);
    doc = xmlReadFile((const char *) URI, NULL, SCHEMATRON_PARSE_OPTIONS);
    if (doc == NULL) {
	xmlSchematronPErr(ctxt, cur,
		      XML_SCHEMAP_FAILED_LOAD,
		      "could not load include '%s'.\n",
		      URI, NULL);
	goto done;
    }
    ret = xmlDocGetRootElement(doc);
    if (ret == NULL) {
	xmlSchematronPErr(ctxt, cur,
		      XML_SCHEMAP_FAILED_LOAD,
		      "could not find root from include '%s'.\n",
		      URI, NULL);
	goto done;
    }

    /* Success, push the include for rollback on exit */
    xmlSchematronPushInclude(ctxt, doc, cur);

done:
    if (ret == NULL) {
        if (doc != NULL)
	    xmlFreeDoc(doc);
    }
    xmlFree(href);
    if (base != NULL)
        xmlFree(base);
    if (URI != NULL)
        xmlFree(URI);
    return(ret);
}
#endif

/**
 * xmlSchematronParse:
 * @ctxt:  a schema validation context
 *
 * parse a schema definition resource and build an internal
 * XML Shema struture which can be used to validate instances.
 *
 * Returns the internal XML Schematron structure built from the resource or
 *         NULL in case of error
 */
xmlSchematronPtr
xmlSchematronParse(xmlSchematronParserCtxtPtr ctxt)
{
    xmlSchematronPtr ret = NULL;
    xmlDocPtr doc;
    xmlNodePtr root, cur;
    int preserve = 0;

    if (ctxt == NULL)
        return (NULL);

    ctxt->nberrors = 0;

    /*
     * First step is to parse the input document into an DOM/Infoset
     */
    if (ctxt->URL != NULL) {
        doc = xmlReadFile((const char *) ctxt->URL, NULL,
	                  SCHEMATRON_PARSE_OPTIONS);
        if (doc == NULL) {
	    xmlSchematronPErr(ctxt, NULL,
			  XML_SCHEMAP_FAILED_LOAD,
                          "xmlSchematronParse: could not load '%s'.\n",
                          ctxt->URL, NULL);
            return (NULL);
        }
	ctxt->preserve = 0;
    } else if (ctxt->buffer != NULL) {
        doc = xmlReadMemory(ctxt->buffer, ctxt->size, NULL, NULL,
	                    SCHEMATRON_PARSE_OPTIONS);
        if (doc == NULL) {
	    xmlSchematronPErr(ctxt, NULL,
			  XML_SCHEMAP_FAILED_PARSE,
                          "xmlSchematronParse: could not parse.\n",
                          NULL, NULL);
            return (NULL);
        }
        doc->URL = xmlStrdup(BAD_CAST "in_memory_buffer");
        ctxt->URL = xmlDictLookup(ctxt->dict, BAD_CAST "in_memory_buffer", -1);
	ctxt->preserve = 0;
    } else if (ctxt->doc != NULL) {
        doc = ctxt->doc;
	preserve = 1;
	ctxt->preserve = 1;
    } else {
	xmlSchematronPErr(ctxt, NULL,
		      XML_SCHEMAP_NOTHING_TO_PARSE,
		      "xmlSchematronParse: could not parse.\n",
		      NULL, NULL);
        return (NULL);
    }

    /*
     * Then extract the root and Schematron parse it
     */
    root = xmlDocGetRootElement(doc);
    if (root == NULL) {
	xmlSchematronPErr(ctxt, (xmlNodePtr) doc,
		      XML_SCHEMAP_NOROOT,
		      "The schema has no document element.\n", NULL, NULL);
	if (!preserve) {
	    xmlFreeDoc(doc);
	}
        return (NULL);
    }

    if (!IS_SCHEMATRON(root, "schema")) {
	xmlSchematronPErr(ctxt, root,
	    XML_SCHEMAP_NOROOT,
	    "The XML document '%s' is not a XML schematron document",
	    ctxt->URL, NULL);
	goto exit;
    }
    ret = xmlSchematronNewSchematron(ctxt);
    if (ret == NULL)
        goto exit;
    ctxt->schema = ret;

    /*
     * scan the schema elements
     */
    cur = root->children;
    NEXT_SCHEMATRON(cur);
    if (IS_SCHEMATRON(cur, "title")) {
        xmlChar *title = xmlNodeGetContent(cur);
	if (title != NULL) {
	    ret->title = xmlDictLookup(ret->dict, title, -1);
	    xmlFree(title);
	}
	cur = cur->next;
	NEXT_SCHEMATRON(cur);
    }
    while (IS_SCHEMATRON(cur, "ns")) {
        xmlChar *prefix = xmlGetNoNsProp(cur, BAD_CAST "prefix");
        xmlChar *uri = xmlGetNoNsProp(cur, BAD_CAST "uri");
	if ((uri == NULL) || (uri[0] == 0)) {
	    xmlSchematronPErr(ctxt, cur,
		XML_SCHEMAP_NOROOT,
		"ns element has no uri", NULL, NULL);
	}
	if ((prefix == NULL) || (prefix[0] == 0)) {
	    xmlSchematronPErr(ctxt, cur,
		XML_SCHEMAP_NOROOT,
		"ns element has no prefix", NULL, NULL);
	}
	if ((prefix) && (uri)) {
	    xmlXPathRegisterNs(ctxt->xctxt, prefix, uri);
	    xmlSchematronAddNamespace(ctxt, prefix, uri);
	    ret->nbNs++;
	}
	if (uri)
	    xmlFree(uri);
	if (prefix)
	    xmlFree(prefix);
	cur = cur->next;
	NEXT_SCHEMATRON(cur);
    }
    while (cur != NULL) {
	if (IS_SCHEMATRON(cur, "pattern")) {
	    xmlSchematronParsePattern(ctxt, cur);
	    ret->nbPattern++;
	} else {
	    xmlSchematronPErr(ctxt, cur,
		XML_SCHEMAP_NOROOT,
		"Expecting a pattern element instead of %s", cur->name, NULL);
	}
	cur = cur->next;
	NEXT_SCHEMATRON(cur);
    }
    if (ret->nbPattern == 0) {
	xmlSchematronPErr(ctxt, root,
	    XML_SCHEMAP_NOROOT,
	    "The schematron document '%s' has no pattern",
	    ctxt->URL, NULL);
	goto exit;
    }
    /* the original document must be kept for reporting */
    ret->doc = doc;
    if (preserve) {
	    ret->preserve = 1;
    }
    preserve = 1;

exit:
    if (!preserve) {
	xmlFreeDoc(doc);
    }
    if (ret != NULL) {
	if (ctxt->nberrors != 0) {
	    xmlSchematronFree(ret);
	    ret = NULL;
	} else {
	    ret->namespaces = ctxt->namespaces;
	    ret->nbNamespaces = ctxt->nbNamespaces;
	    ctxt->namespaces = NULL;
	}
    }
    return (ret);
}

/************************************************************************
 *									*
 *		Schematrontron Reports handler				*
 *									*
 ************************************************************************/

static xmlNodePtr
xmlSchematronGetNode(xmlSchematronValidCtxtPtr ctxt,
                     xmlNodePtr cur, const xmlChar *xpath) {
    xmlNodePtr node = NULL;
    xmlXPathObjectPtr ret;

    if ((ctxt == NULL) || (cur == NULL) || (xpath == NULL))
        return(NULL);

    ctxt->xctxt->doc = cur->doc;
    ctxt->xctxt->node = cur;
    ret = xmlXPathEval(xpath, ctxt->xctxt);
    if (ret == NULL)
        return(NULL);

    if ((ret->type == XPATH_NODESET) &&
        (ret->nodesetval != NULL) && (ret->nodesetval->nodeNr > 0))
	node = ret->nodesetval->nodeTab[0];

    xmlXPathFreeObject(ret);
    return(node);
}

/**
 * xmlSchematronReportOutput:
 * @ctxt: the validation context
 * @cur: the current node tested
 * @msg: the message output
 *
 * Output part of the report to whatever channel the user selected
 */
static void
xmlSchematronReportOutput(xmlSchematronValidCtxtPtr ctxt ATTRIBUTE_UNUSED,
                          xmlNodePtr cur ATTRIBUTE_UNUSED,
                          const char *msg) {
    /* TODO */
    fprintf(stderr, "%s", msg);
}

/**
 * xmlSchematronFormatReport:
 * @ctxt:  the validation context
 * @test: the test node
 * @cur: the current node tested
 *
 * Build the string being reported to the user.
 *
 * Returns a report string or NULL in case of error. The string needs
 *         to be deallocated by teh caller
 */
static xmlChar *
xmlSchematronFormatReport(xmlSchematronValidCtxtPtr ctxt,
			  xmlNodePtr test, xmlNodePtr cur) {
    xmlChar *ret = NULL;
    xmlNodePtr child, node;

    if ((test == NULL) || (cur == NULL))
        return(ret);

    child = test->children;
    while (child != NULL) {
        if ((child->type == XML_TEXT_NODE) ||
	    (child->type == XML_CDATA_SECTION_NODE))
	    ret = xmlStrcat(ret, child->content);
	else if (IS_SCHEMATRON(child, "name")) {
	    xmlChar *path;

	    path = xmlGetNoNsProp(child, BAD_CAST "path");

            node = cur;
	    if (path != NULL) {
	        node = xmlSchematronGetNode(ctxt, cur, path);
		if (node == NULL)
		    node = cur;
		xmlFree(path);
	    }

	    if ((node->ns == NULL) || (node->ns->prefix == NULL))
	        ret = xmlStrcat(ret, node->name);
	    else {
	        ret = xmlStrcat(ret, node->ns->prefix);
	        ret = xmlStrcat(ret, BAD_CAST ":");
	        ret = xmlStrcat(ret, node->name);
	    }
	} else {
	    child = child->next;
	    continue;
	}

	/*
	 * remove superfluous \n
	 */
	if (ret != NULL) {
	    int len = xmlStrlen(ret);
	    xmlChar c;

	    if (len > 0) {
		c = ret[len - 1];
		if ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
		    while ((c == ' ') || (c == '\n') ||
		           (c == '\r') || (c == '\t')) {
			len--;
			if (len == 0)
			    break;
			c = ret[len - 1];
		    }
		    ret[len] = ' ';
		    ret[len + 1] = 0;
		}
	    }
	}

        child = child->next;
    }
    return(ret);
}

/**
 * xmlSchematronReportSuccess:
 * @ctxt:  the validation context
 * @test: the compiled test
 * @cur: the current node tested
 * @success: boolean value for the result
 *
 * called from the validation engine when an assert or report test have
 * been done.
 */
static void
xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt,
		   xmlSchematronTestPtr test, xmlNodePtr cur, xmlSchematronPatternPtr pattern, int success) {
    if ((ctxt == NULL) || (cur == NULL) || (test == NULL))
        return;
    /* if quiet and not SVRL report only failures */
    if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) &&
        ((ctxt->flags & XML_SCHEMATRON_OUT_XML) == 0) &&
	(test->type == XML_SCHEMATRON_REPORT))
        return;
    if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
        TODO
    } else {
        xmlChar *path;
	char msg[1000];
	long line;
	const xmlChar *report = NULL;

        if (((test->type == XML_SCHEMATRON_REPORT) & (!success)) ||
	    ((test->type == XML_SCHEMATRON_ASSERT) & (success)))
	    return;
	line = xmlGetLineNo(cur);
	path = xmlGetNodePath(cur);
	if (path == NULL)
	    path = (xmlChar *) cur->name;
#if 0
	if ((test->report != NULL) && (test->report[0] != 0))
	    report = test->report;
#endif
	if (test->node != NULL)
            report = xmlSchematronFormatReport(ctxt, test->node, cur);
	if (report == NULL) {
	    if (test->type == XML_SCHEMATRON_ASSERT) {
            report = xmlStrdup((const xmlChar *) "node failed assert");
	    } else {
            report = xmlStrdup((const xmlChar *) "node failed report");
	    }
	    }
	    snprintf(msg, 999, "%s line %ld: %s\n", (const char *) path,
		     line, (const char *) report);

    if (ctxt->flags & XML_SCHEMATRON_OUT_ERROR) {
        xmlStructuredErrorFunc schannel = NULL;
        xmlGenericErrorFunc channel = NULL;
        void *data = NULL;

        if (ctxt != NULL) {
            if (ctxt->serror != NULL)
                schannel = ctxt->serror;
            else
                channel = ctxt->error;
            data = ctxt->userData;
	}

        __xmlRaiseError(schannel, channel, data,
                        NULL, cur, XML_FROM_SCHEMATRONV,
                        (test->type == XML_SCHEMATRON_ASSERT)?XML_SCHEMATRONV_ASSERT:XML_SCHEMATRONV_REPORT,
                        XML_ERR_ERROR, NULL, line,
                        (pattern == NULL)?NULL:((const char *) pattern->name),
                        (const char *) path,
                        (const char *) report, 0, 0,
                        "%s", msg);
    } else {
	xmlSchematronReportOutput(ctxt, cur, &msg[0]);
    }

    xmlFree((char *) report);

	if ((path != NULL) && (path != (xmlChar *) cur->name))
	    xmlFree(path);
    }
}

/**
 * xmlSchematronReportPattern:
 * @ctxt:  the validation context
 * @pattern: the current pattern
 *
 * called from the validation engine when starting to check a pattern
 */
static void
xmlSchematronReportPattern(xmlSchematronValidCtxtPtr ctxt,
			   xmlSchematronPatternPtr pattern) {
    if ((ctxt == NULL) || (pattern == NULL))
        return;
    if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) || (ctxt->flags & XML_SCHEMATRON_OUT_ERROR)) /* Error gives pattern name as part of error */
        return;
    if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
        TODO
    } else {
	char msg[1000];

	if (pattern->name == NULL)
	    return;
	snprintf(msg, 999, "Pattern: %s\n", (const char *) pattern->name);
	xmlSchematronReportOutput(ctxt, NULL, &msg[0]);
    }
}

/************************************************************************
 *									*
 *		Validation against a Schematrontron				*
 *									*
 ************************************************************************/

/**
 * xmlSchematronSetValidStructuredErrors:
 * @ctxt:  a Schematron validation context
 * @serror:  the structured error function
 * @ctx: the functions context
 *
 * Set the structured error callback
 */
void
xmlSchematronSetValidStructuredErrors(xmlSchematronValidCtxtPtr ctxt,
                                      xmlStructuredErrorFunc serror, void *ctx)
{
    if (ctxt == NULL)
        return;
    ctxt->serror = serror;
    ctxt->error = NULL;
    ctxt->warning = NULL;
    ctxt->userData = ctx;
}

/**
 * xmlSchematronNewValidCtxt:
 * @schema:  a precompiled XML Schematrons
 * @options: a set of xmlSchematronValidOptions
 *
 * Create an XML Schematrons validation context based on the given schema.
 *
 * Returns the validation context or NULL in case of error
 */
xmlSchematronValidCtxtPtr
xmlSchematronNewValidCtxt(xmlSchematronPtr schema, int options)
{
    int i;
    xmlSchematronValidCtxtPtr ret;

    ret = (xmlSchematronValidCtxtPtr) xmlMalloc(sizeof(xmlSchematronValidCtxt));
    if (ret == NULL) {
        xmlSchematronVErrMemory(NULL, "allocating validation context",
                                NULL);
        return (NULL);
    }
    memset(ret, 0, sizeof(xmlSchematronValidCtxt));
    ret->type = XML_STRON_CTXT_VALIDATOR;
    ret->schema = schema;
    ret->xctxt = xmlXPathNewContext(NULL);
    ret->flags = options;
    if (ret->xctxt == NULL) {
        xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
                                NULL);
	xmlSchematronFreeValidCtxt(ret);
        return (NULL);
    }
    for (i = 0;i < schema->nbNamespaces;i++) {
        if ((schema->namespaces[2 * i] == NULL) ||
            (schema->namespaces[2 * i + 1] == NULL))
	    break;
	xmlXPathRegisterNs(ret->xctxt, schema->namespaces[2 * i + 1],
	                   schema->namespaces[2 * i]);
    }
    return (ret);
}

/**
 * xmlSchematronFreeValidCtxt:
 * @ctxt:  the schema validation context
 *
 * Free the resources associated to the schema validation context
 */
void
xmlSchematronFreeValidCtxt(xmlSchematronValidCtxtPtr ctxt)
{
    if (ctxt == NULL)
        return;
    if (ctxt->xctxt != NULL)
        xmlXPathFreeContext(ctxt->xctxt);
    if (ctxt->dict != NULL)
        xmlDictFree(ctxt->dict);
    xmlFree(ctxt);
}

static xmlNodePtr
xmlSchematronNextNode(xmlNodePtr cur) {
    if (cur->children != NULL) {
	/*
	 * Do not descend on entities declarations
	 */
	if (cur->children->type != XML_ENTITY_DECL) {
	    cur = cur->children;
	    /*
	     * Skip DTDs
	     */
	    if (cur->type != XML_DTD_NODE)
		return(cur);
	}
    }

    while (cur->next != NULL) {
	cur = cur->next;
	if ((cur->type != XML_ENTITY_DECL) &&
	    (cur->type != XML_DTD_NODE))
	    return(cur);
    }

    do {
	cur = cur->parent;
	if (cur == NULL) break;
	if (cur->type == XML_DOCUMENT_NODE) return(NULL);
	if (cur->next != NULL) {
	    cur = cur->next;
	    return(cur);
	}
    } while (cur != NULL);
    return(cur);
}

/**
 * xmlSchematronRunTest:
 * @ctxt:  the schema validation context
 * @test:  the current test
 * @instance:  the document instace tree
 * @cur:  the current node in the instance
 *
 * Validate a rule against a tree instance at a given position
 *
 * Returns 1 in case of success, 0 if error and -1 in case of internal error
 */
static int
xmlSchematronRunTest(xmlSchematronValidCtxtPtr ctxt,
     xmlSchematronTestPtr test, xmlDocPtr instance, xmlNodePtr cur, xmlSchematronPatternPtr pattern)
{
    xmlXPathObjectPtr ret;
    int failed;

    failed = 0;
    ctxt->xctxt->doc = instance;
    ctxt->xctxt->node = cur;
    ret = xmlXPathCompiledEval(test->comp, ctxt->xctxt);
    if (ret == NULL) {
	failed = 1;
    } else {
        switch (ret->type) {
	    case XPATH_XSLT_TREE:
	    case XPATH_NODESET:
		if ((ret->nodesetval == NULL) ||
		    (ret->nodesetval->nodeNr == 0))
		    failed = 1;
		break;
	    case XPATH_BOOLEAN:
		failed = !ret->boolval;
		break;
	    case XPATH_NUMBER:
		if ((xmlXPathIsNaN(ret->floatval)) ||
		    (ret->floatval == 0.0))
		    failed = 1;
		break;
	    case XPATH_STRING:
		if ((ret->stringval == NULL) ||
		    (ret->stringval[0] == 0))
		    failed = 1;
		break;
	    case XPATH_UNDEFINED:
	    case XPATH_POINT:
	    case XPATH_RANGE:
	    case XPATH_LOCATIONSET:
	    case XPATH_USERS:
		failed = 1;
		break;
	}
	xmlXPathFreeObject(ret);
    }
    if ((failed) && (test->type == XML_SCHEMATRON_ASSERT))
        ctxt->nberrors++;
    else if ((!failed) && (test->type == XML_SCHEMATRON_REPORT))
        ctxt->nberrors++;

    xmlSchematronReportSuccess(ctxt, test, cur, pattern, !failed);

    return(!failed);
}

/**
 * xmlSchematronValidateDoc:
 * @ctxt:  the schema validation context
 * @instance:  the document instace tree
 *
 * Validate a tree instance against the schematron
 *
 * Returns 0 in case of success, -1 in case of internal error
 *         and an error count otherwise.
 */
int
xmlSchematronValidateDoc(xmlSchematronValidCtxtPtr ctxt, xmlDocPtr instance)
{
    xmlNodePtr cur, root;
    xmlSchematronPatternPtr pattern;
    xmlSchematronRulePtr rule;
    xmlSchematronTestPtr test;

    if ((ctxt == NULL) || (ctxt->schema == NULL) ||
        (ctxt->schema->rules == NULL) || (instance == NULL))
        return(-1);
    ctxt->nberrors = 0;
    root = xmlDocGetRootElement(instance);
    if (root == NULL) {
        TODO
	ctxt->nberrors++;
	return(1);
    }
    if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) ||
        (ctxt->flags == 0)) {
	/*
	 * we are just trying to assert the validity of the document,
	 * speed primes over the output, run in a single pass
	 */
	cur = root;
	while (cur != NULL) {
	    rule = ctxt->schema->rules;
	    while (rule != NULL) {
		if (xmlPatternMatch(rule->pattern, cur) == 1) {
		    test = rule->tests;
		    while (test != NULL) {
			xmlSchematronRunTest(ctxt, test, instance, cur, (xmlSchematronPatternPtr)rule->pattern);
			test = test->next;
		    }
		}
		rule = rule->next;
	    }

	    cur = xmlSchematronNextNode(cur);
	}
    } else {
        /*
	 * Process all contexts one at a time
	 */
	pattern = ctxt->schema->patterns;

	while (pattern != NULL) {
	    xmlSchematronReportPattern(ctxt, pattern);

	    /*
	     * TODO convert the pattern rule to a direct XPath and
	     * compute directly instead of using the pattern matching
	     * over the full document...
	     * Check the exact semantic
	     */
	    cur = root;
	    while (cur != NULL) {
		rule = pattern->rules;
		while (rule != NULL) {
		    if (xmlPatternMatch(rule->pattern, cur) == 1) {
			test = rule->tests;
			while (test != NULL) {
			    xmlSchematronRunTest(ctxt, test, instance, cur, pattern);
			    test = test->next;
			}
		    }
		    rule = rule->patnext;
		}

		cur = xmlSchematronNextNode(cur);
	    }
	    pattern = pattern->next;
	}
    }
    return(ctxt->nberrors);
}

#ifdef STANDALONE
int
main(void)
{
    int ret;
    xmlDocPtr instance;
    xmlSchematronParserCtxtPtr pctxt;
    xmlSchematronValidCtxtPtr vctxt;
    xmlSchematronPtr schema = NULL;

    pctxt = xmlSchematronNewParserCtxt("tst.sct");
    if (pctxt == NULL) {
        fprintf(stderr, "failed to build schematron parser\n");
    } else {
        schema = xmlSchematronParse(pctxt);
	if (schema == NULL) {
	    fprintf(stderr, "failed to compile schematron\n");
	}
	xmlSchematronFreeParserCtxt(pctxt);
    }
    instance = xmlReadFile("tst.sct", NULL,
                           XML_PARSE_NOENT | XML_PARSE_NOCDATA);
    if (instance == NULL) {
	fprintf(stderr, "failed to parse instance\n");
    }
    if ((schema != NULL) && (instance != NULL)) {
        vctxt = xmlSchematronNewValidCtxt(schema);
	if (vctxt == NULL) {
	    fprintf(stderr, "failed to build schematron validator\n");
	} else {
	    ret = xmlSchematronValidateDoc(vctxt, instance);
	    xmlSchematronFreeValidCtxt(vctxt);
	}
    }
    xmlSchematronFree(schema);
    xmlFreeDoc(instance);

    xmlCleanupParser();
    xmlMemoryDump();

    return (0);
}
#endif
#define bottom_schematron
#include "elfgcchack.h"
#endif /* LIBXML_SCHEMATRON_ENABLED */


code:
/**
 * threads.c: set of generic threading related routines
 *
 * See Copyright for the status of this software.
 *
 * Gary Pennington <Gary.Pennington@uk.sun.com>
 * daniel@veillard.com
 */

#define IN_LIBXML
#include "libxml.h"

#include <string.h>

#include <libxml/threads.h>
#include <libxml/globals.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#ifdef HAVE_PTHREAD_H
#include <pthread.h>
#elif defined HAVE_WIN32_THREADS
#include <windows.h>
#ifndef HAVE_COMPILER_TLS
#include <process.h>
#endif
#endif

#ifdef HAVE_BEOS_THREADS
#include <OS.h>
#include <TLS.h>
#endif

#if defined(SOLARIS)
#include <note.h>
#endif

/* #define DEBUG_THREADS */

#ifdef HAVE_PTHREAD_H

static int libxml_is_threaded = -1;
#ifdef __GNUC__
#ifdef linux
#if (__GNUC__ == 3 && __GNUC_MINOR__ >= 3) || (__GNUC__ > 3)
extern int pthread_once (pthread_once_t *__once_control,
                         void (*__init_routine) (void))
	   __attribute((weak));
extern void *pthread_getspecific (pthread_key_t __key)
	   __attribute((weak));
extern int pthread_setspecific (pthread_key_t __key,
                                __const void *__pointer)
	   __attribute((weak));
extern int pthread_key_create (pthread_key_t *__key,
                               void (*__destr_function) (void *))
	   __attribute((weak));
extern int pthread_key_delete (pthread_key_t __key)
	   __attribute((weak));
extern int pthread_mutex_init ()
	   __attribute((weak));
extern int pthread_mutex_destroy ()
	   __attribute((weak));
extern int pthread_mutex_lock ()
	   __attribute((weak));
extern int pthread_mutex_unlock ()
	   __attribute((weak));
extern int pthread_cond_init ()
	   __attribute((weak));
extern int pthread_cond_destroy ()
	   __attribute((weak));
extern int pthread_cond_wait ()
	   __attribute((weak));
extern int pthread_equal ()
	   __attribute((weak));
extern pthread_t pthread_self ()
	   __attribute((weak));
extern int pthread_key_create ()
	   __attribute((weak));
extern int pthread_key_delete ()
	   __attribute((weak));
extern int pthread_cond_signal ()
	   __attribute((weak));
#endif
#endif /* linux */
#endif /* __GNUC__ */
#endif /* HAVE_PTHREAD_H */

/*
 * TODO: this module still uses malloc/free and not xmlMalloc/xmlFree
 *       to avoid some crazyness since xmlMalloc/xmlFree may actually
 *       be hosted on allocated blocks needing them for the allocation ...
 */

/*
 * xmlMutex are a simple mutual exception locks
 */
struct _xmlMutex {
#ifdef HAVE_PTHREAD_H
    pthread_mutex_t lock;
#elif defined HAVE_WIN32_THREADS
    HANDLE mutex;
#elif defined HAVE_BEOS_THREADS
    sem_id sem;
    thread_id tid;
#else
    int empty;
#endif
};

/*
 * xmlRMutex are reentrant mutual exception locks
 */
struct _xmlRMutex {
#ifdef HAVE_PTHREAD_H
    pthread_mutex_t lock;
    unsigned int held;
    unsigned int waiters;
    pthread_t tid;
    pthread_cond_t cv;
#elif defined HAVE_WIN32_THREADS
    CRITICAL_SECTION cs;
    unsigned int count;
#elif defined HAVE_BEOS_THREADS
    xmlMutexPtr lock;
    thread_id tid;
    int32 count;
#else
    int empty;
#endif
};

/*
 * This module still has some internal static data.
 *   - xmlLibraryLock a global lock
 *   - globalkey used for per-thread data
 */

#ifdef HAVE_PTHREAD_H
static pthread_key_t globalkey;
static pthread_t mainthread;
static pthread_once_t once_control = PTHREAD_ONCE_INIT;
static pthread_mutex_t global_init_lock = PTHREAD_MUTEX_INITIALIZER;
#elif defined HAVE_WIN32_THREADS
#if defined(HAVE_COMPILER_TLS)
static __declspec(thread) xmlGlobalState tlstate;
static __declspec(thread) int tlstate_inited = 0;
#else /* HAVE_COMPILER_TLS */
static DWORD globalkey = TLS_OUT_OF_INDEXES;
#endif /* HAVE_COMPILER_TLS */
static DWORD mainthread;
static struct {
    DWORD done;
    DWORD control;
} run_once = { 0, 0};
static volatile LPCRITICAL_SECTION global_init_lock = NULL;

/* endif HAVE_WIN32_THREADS */
#elif defined HAVE_BEOS_THREADS
int32 globalkey = 0;
thread_id mainthread = 0;
int32 run_once_init = 0;
static int32 global_init_lock = -1;
static vint32 global_init_count = 0;
#endif

static xmlRMutexPtr xmlLibraryLock = NULL;

#ifdef LIBXML_THREAD_ENABLED
static void xmlOnceInit(void);
#endif

/**
 * xmlNewMutex:
 *
 * xmlNewMutex() is used to allocate a libxml2 token struct for use in
 * synchronizing access to data.
 *
 * Returns a new simple mutex pointer or NULL in case of error
 */
xmlMutexPtr
xmlNewMutex(void)
{
    xmlMutexPtr tok;

    if ((tok = malloc(sizeof(xmlMutex))) == NULL)
        return (NULL);
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded != 0)
        pthread_mutex_init(&tok->lock, NULL);
#elif defined HAVE_WIN32_THREADS
    tok->mutex = CreateMutex(NULL, FALSE, NULL);
#elif defined HAVE_BEOS_THREADS
    if ((tok->sem = create_sem(1, "xmlMutex")) < B_OK) {
        free(tok);
        return NULL;
    }
    tok->tid = -1;
#endif
    return (tok);
}

/**
 * xmlFreeMutex:
 * @tok:  the simple mutex
 *
 * xmlFreeMutex() is used to reclaim resources associated with a libxml2 token
 * struct.
 */
void
xmlFreeMutex(xmlMutexPtr tok)
{
    if (tok == NULL)
        return;

#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded != 0)
        pthread_mutex_destroy(&tok->lock);
#elif defined HAVE_WIN32_THREADS
    CloseHandle(tok->mutex);
#elif defined HAVE_BEOS_THREADS
    delete_sem(tok->sem);
#endif
    free(tok);
}

/**
 * xmlMutexLock:
 * @tok:  the simple mutex
 *
 * xmlMutexLock() is used to lock a libxml2 token.
 */
void
xmlMutexLock(xmlMutexPtr tok)
{
    if (tok == NULL)
        return;
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded != 0)
        pthread_mutex_lock(&tok->lock);
#elif defined HAVE_WIN32_THREADS
    WaitForSingleObject(tok->mutex, INFINITE);
#elif defined HAVE_BEOS_THREADS
    if (acquire_sem(tok->sem) != B_NO_ERROR) {
#ifdef DEBUG_THREADS
        xmlGenericError(xmlGenericErrorContext,
                        "xmlMutexLock():BeOS:Couldn't aquire semaphore\n");
        exit();
#endif
    }
    tok->tid = find_thread(NULL);
#endif

}

/**
 * xmlMutexUnlock:
 * @tok:  the simple mutex
 *
 * xmlMutexUnlock() is used to unlock a libxml2 token.
 */
void
xmlMutexUnlock(xmlMutexPtr tok)
{
    if (tok == NULL)
        return;
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded != 0)
        pthread_mutex_unlock(&tok->lock);
#elif defined HAVE_WIN32_THREADS
    ReleaseMutex(tok->mutex);
#elif defined HAVE_BEOS_THREADS
    if (tok->tid == find_thread(NULL)) {
        tok->tid = -1;
        release_sem(tok->sem);
    }
#endif
}

/**
 * xmlNewRMutex:
 *
 * xmlRNewMutex() is used to allocate a reentrant mutex for use in
 * synchronizing access to data. token_r is a re-entrant lock and thus useful
 * for synchronizing access to data structures that may be manipulated in a
 * recursive fashion.
 *
 * Returns the new reentrant mutex pointer or NULL in case of error
 */
xmlRMutexPtr
xmlNewRMutex(void)
{
    xmlRMutexPtr tok;

    if ((tok = malloc(sizeof(xmlRMutex))) == NULL)
        return (NULL);
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded != 0) {
        pthread_mutex_init(&tok->lock, NULL);
        tok->held = 0;
        tok->waiters = 0;
        pthread_cond_init(&tok->cv, NULL);
    }
#elif defined HAVE_WIN32_THREADS
    InitializeCriticalSection(&tok->cs);
    tok->count = 0;
#elif defined HAVE_BEOS_THREADS
    if ((tok->lock = xmlNewMutex()) == NULL) {
        free(tok);
        return NULL;
    }
    tok->count = 0;
#endif
    return (tok);
}

/**
 * xmlFreeRMutex:
 * @tok:  the reentrant mutex
 *
 * xmlRFreeMutex() is used to reclaim resources associated with a
 * reentrant mutex.
 */
void
xmlFreeRMutex(xmlRMutexPtr tok ATTRIBUTE_UNUSED)
{
    if (tok == NULL)
        return;
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded != 0) {
        pthread_mutex_destroy(&tok->lock);
        pthread_cond_destroy(&tok->cv);
    }
#elif defined HAVE_WIN32_THREADS
    DeleteCriticalSection(&tok->cs);
#elif defined HAVE_BEOS_THREADS
    xmlFreeMutex(tok->lock);
#endif
    free(tok);
}

/**
 * xmlRMutexLock:
 * @tok:  the reentrant mutex
 *
 * xmlRMutexLock() is used to lock a libxml2 token_r.
 */
void
xmlRMutexLock(xmlRMutexPtr tok)
{
    if (tok == NULL)
        return;
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded == 0)
        return;

    pthread_mutex_lock(&tok->lock);
    if (tok->held) {
        if (pthread_equal(tok->tid, pthread_self())) {
            tok->held++;
            pthread_mutex_unlock(&tok->lock);
            return;
        } else {
            tok->waiters++;
            while (tok->held)
                pthread_cond_wait(&tok->cv, &tok->lock);
            tok->waiters--;
        }
    }
    tok->tid = pthread_self();
    tok->held = 1;
    pthread_mutex_unlock(&tok->lock);
#elif defined HAVE_WIN32_THREADS
    EnterCriticalSection(&tok->cs);
    ++tok->count;
#elif defined HAVE_BEOS_THREADS
    if (tok->lock->tid == find_thread(NULL)) {
        tok->count++;
        return;
    } else {
        xmlMutexLock(tok->lock);
        tok->count = 1;
    }
#endif
}

/**
 * xmlRMutexUnlock:
 * @tok:  the reentrant mutex
 *
 * xmlRMutexUnlock() is used to unlock a libxml2 token_r.
 */
void
xmlRMutexUnlock(xmlRMutexPtr tok ATTRIBUTE_UNUSED)
{
    if (tok == NULL)
        return;
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded == 0)
        return;

    pthread_mutex_lock(&tok->lock);
    tok->held--;
    if (tok->held == 0) {
        if (tok->waiters)
            pthread_cond_signal(&tok->cv);
        memset(&tok->tid, 0, sizeof(tok->tid));
    }
    pthread_mutex_unlock(&tok->lock);
#elif defined HAVE_WIN32_THREADS
    if (!--tok->count)
        LeaveCriticalSection(&tok->cs);
#elif defined HAVE_BEOS_THREADS
    if (tok->lock->tid == find_thread(NULL)) {
        tok->count--;
        if (tok->count == 0) {
            xmlMutexUnlock(tok->lock);
        }
        return;
    }
#endif
}

/**
 * xmlGlobalInitMutexLock
 *
 * Makes sure that the global initialization mutex is initialized and
 * locks it.
 */
void
__xmlGlobalInitMutexLock(void)
{
    /* Make sure the global init lock is initialized and then lock it. */
#ifdef HAVE_PTHREAD_H
    /* The mutex is statically initialized, so we just lock it. */
    if (pthread_mutex_lock)
        pthread_mutex_lock(&global_init_lock);
#elif defined HAVE_WIN32_THREADS
    LPCRITICAL_SECTION cs;

    /* Create a new critical section */
    if (global_init_lock == NULL) {
        cs = malloc(sizeof(CRITICAL_SECTION));
        if (cs == NULL) {
            xmlGenericError(xmlGenericErrorContext,
                            "xmlGlobalInitMutexLock: out of memory\n");
            return;
        }
        InitializeCriticalSection(cs);

        /* Swap it into the global_init_lock */
#ifdef InterlockedCompareExchangePointer
        InterlockedCompareExchangePointer(&global_init_lock, cs, NULL);
#else /* Use older void* version */
        InterlockedCompareExchange((void **) &global_init_lock,
                                   (void *) cs, NULL);
#endif /* InterlockedCompareExchangePointer */

        /* If another thread successfully recorded its critical
         * section in the global_init_lock then discard the one
         * allocated by this thread. */
        if (global_init_lock != cs) {
            DeleteCriticalSection(cs);
            free(cs);
        }
    }

    /* Lock the chosen critical section */
    EnterCriticalSection(global_init_lock);
#elif defined HAVE_BEOS_THREADS
    int32 sem;

    /* Allocate a new semaphore */
    sem = create_sem(1, "xmlGlobalinitMutex");

    while (global_init_lock == -1) {
        if (atomic_add(&global_init_count, 1) == 0) {
            global_init_lock = sem;
        } else {
            snooze(1);
            atomic_add(&global_init_count, -1);
        }
    }

    /* If another thread successfully recorded its critical
     * section in the global_init_lock then discard the one
     * allocated by this thread. */
    if (global_init_lock != sem)
        delete_sem(sem);

    /* Acquire the chosen semaphore */
    if (acquire_sem(global_init_lock) != B_NO_ERROR) {
#ifdef DEBUG_THREADS
        xmlGenericError(xmlGenericErrorContext,
                        "xmlGlobalInitMutexLock():BeOS:Couldn't acquire semaphore\n");
        exit();
#endif
    }
#endif
}

void
__xmlGlobalInitMutexUnlock(void)
{
#ifdef HAVE_PTHREAD_H
    if (pthread_mutex_unlock)
        pthread_mutex_unlock(&global_init_lock);
#elif defined HAVE_WIN32_THREADS
    if (global_init_lock != NULL) {
	LeaveCriticalSection(global_init_lock);
    }
#elif defined HAVE_BEOS_THREADS
    release_sem(global_init_lock);
#endif
}

/**
 * xmlGlobalInitMutexDestroy
 *
 * Makes sure that the global initialization mutex is destroyed before
 * application termination.
 */
void
__xmlGlobalInitMutexDestroy(void)
{
#ifdef HAVE_PTHREAD_H
#elif defined HAVE_WIN32_THREADS
    if (global_init_lock != NULL) {
        DeleteCriticalSection(global_init_lock);
        free(global_init_lock);
        global_init_lock = NULL;
    }
#endif
}

/************************************************************************
 *									*
 *			Per thread global state handling		*
 *									*
 ************************************************************************/

#ifdef LIBXML_THREAD_ENABLED
#ifdef xmlLastError
#undef xmlLastError
#endif

/**
 * xmlFreeGlobalState:
 * @state:  a thread global state
 *
 * xmlFreeGlobalState() is called when a thread terminates with a non-NULL
 * global state. It is is used here to reclaim memory resources.
 */
static void
xmlFreeGlobalState(void *state)
{
    xmlGlobalState *gs = (xmlGlobalState *) state;

    /* free any memory allocated in the thread's xmlLastError */
    xmlResetError(&(gs->xmlLastError));
    free(state);
}

/**
 * xmlNewGlobalState:
 *
 * xmlNewGlobalState() allocates a global state. This structure is used to
 * hold all data for use by a thread when supporting backwards compatibility
 * of libxml2 to pre-thread-safe behaviour.
 *
 * Returns the newly allocated xmlGlobalStatePtr or NULL in case of error
 */
static xmlGlobalStatePtr
xmlNewGlobalState(void)
{
    xmlGlobalState *gs;

    gs = malloc(sizeof(xmlGlobalState));
    if (gs == NULL) {
	xmlGenericError(xmlGenericErrorContext,
			"xmlGetGlobalState: out of memory\n");
        return (NULL);
    }

    memset(gs, 0, sizeof(xmlGlobalState));
    xmlInitializeGlobalState(gs);
    return (gs);
}
#endif /* LIBXML_THREAD_ENABLED */

#ifdef HAVE_PTHREAD_H
#elif defined HAVE_WIN32_THREADS
#if !defined(HAVE_COMPILER_TLS)
#if defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL)
typedef struct _xmlGlobalStateCleanupHelperParams {
    HANDLE thread;
    void *memory;
} xmlGlobalStateCleanupHelperParams;

static void XMLCDECL
xmlGlobalStateCleanupHelper(void *p)
{
    xmlGlobalStateCleanupHelperParams *params =
        (xmlGlobalStateCleanupHelperParams *) p;
    WaitForSingleObject(params->thread, INFINITE);
    CloseHandle(params->thread);
    xmlFreeGlobalState(params->memory);
    free(params);
    _endthread();
}
#else /* LIBXML_STATIC && !LIBXML_STATIC_FOR_DLL */

typedef struct _xmlGlobalStateCleanupHelperParams {
    void *memory;
    struct _xmlGlobalStateCleanupHelperParams *prev;
    struct _xmlGlobalStateCleanupHelperParams *next;
} xmlGlobalStateCleanupHelperParams;

static xmlGlobalStateCleanupHelperParams *cleanup_helpers_head = NULL;
static CRITICAL_SECTION cleanup_helpers_cs;

#endif /* LIBXMLSTATIC && !LIBXML_STATIC_FOR_DLL */
#endif /* HAVE_COMPILER_TLS */
#endif /* HAVE_WIN32_THREADS */

#if defined HAVE_BEOS_THREADS

/**
 * xmlGlobalStateCleanup:
 * @data: unused parameter
 *
 * Used for Beos only
 */
void
xmlGlobalStateCleanup(void *data)
{
    void *globalval = tls_get(globalkey);

    if (globalval != NULL)
        xmlFreeGlobalState(globalval);
}
#endif

/**
 * xmlGetGlobalState:
 *
 * xmlGetGlobalState() is called to retrieve the global state for a thread.
 *
 * Returns the thread global state or NULL in case of error
 */
xmlGlobalStatePtr
xmlGetGlobalState(void)
{
#ifdef HAVE_PTHREAD_H
    xmlGlobalState *globalval;

    if (libxml_is_threaded == 0)
        return (NULL);

    pthread_once(&once_control, xmlOnceInit);

    if ((globalval = (xmlGlobalState *)
         pthread_getspecific(globalkey)) == NULL) {
        xmlGlobalState *tsd = xmlNewGlobalState();
	if (tsd == NULL)
	    return(NULL);

        pthread_setspecific(globalkey, tsd);
        return (tsd);
    }
    return (globalval);
#elif defined HAVE_WIN32_THREADS
#if defined(HAVE_COMPILER_TLS)
    if (!tlstate_inited) {
        tlstate_inited = 1;
        xmlInitializeGlobalState(&tlstate);
    }
    return &tlstate;
#else /* HAVE_COMPILER_TLS */
    xmlGlobalState *globalval;
    xmlGlobalStateCleanupHelperParams *p;

    xmlOnceInit();
#if defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL)
    globalval = (xmlGlobalState *) TlsGetValue(globalkey);
#else
    p = (xmlGlobalStateCleanupHelperParams *) TlsGetValue(globalkey);
    globalval = (xmlGlobalState *) (p ? p->memory : NULL);
#endif
    if (globalval == NULL) {
        xmlGlobalState *tsd = xmlNewGlobalState();

        if (tsd == NULL)
	    return(NULL);
        p = (xmlGlobalStateCleanupHelperParams *)
            malloc(sizeof(xmlGlobalStateCleanupHelperParams));
	if (p == NULL) {
            xmlGenericError(xmlGenericErrorContext,
                            "xmlGetGlobalState: out of memory\n");
            xmlFreeGlobalState(tsd);
	    return(NULL);
	}
        p->memory = tsd;
#if defined(LIBXML_STATIC) && !defined(LIBXML_STATIC_FOR_DLL)
        DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
                        GetCurrentProcess(), &p->thread, 0, TRUE,
                        DUPLICATE_SAME_ACCESS);
        TlsSetValue(globalkey, tsd);
        _beginthread(xmlGlobalStateCleanupHelper, 0, p);
#else
        EnterCriticalSection(&cleanup_helpers_cs);
        if (cleanup_helpers_head != NULL) {
            cleanup_helpers_head->prev = p;
        }
        p->next = cleanup_helpers_head;
        p->prev = NULL;
        cleanup_helpers_head = p;
        TlsSetValue(globalkey, p);
        LeaveCriticalSection(&cleanup_helpers_cs);
#endif

        return (tsd);
    }
    return (globalval);
#endif /* HAVE_COMPILER_TLS */
#elif defined HAVE_BEOS_THREADS
    xmlGlobalState *globalval;

    xmlOnceInit();

    if ((globalval = (xmlGlobalState *) tls_get(globalkey)) == NULL) {
        xmlGlobalState *tsd = xmlNewGlobalState();
	if (tsd == NULL)
	    return (NULL);

        tls_set(globalkey, tsd);
        on_exit_thread(xmlGlobalStateCleanup, NULL);
        return (tsd);
    }
    return (globalval);
#else
    return (NULL);
#endif
}

/************************************************************************
 *									*
 *			Library wide thread interfaces			*
 *									*
 ************************************************************************/

/**
 * xmlGetThreadId:
 *
 * xmlGetThreadId() find the current thread ID number
 * Note that this is likely to be broken on some platforms using pthreads
 * as the specification doesn't mandate pthread_t to be an integer type
 *
 * Returns the current thread ID number
 */
int
xmlGetThreadId(void)
{
#ifdef HAVE_PTHREAD_H
    pthread_t id;
    int ret;

    if (libxml_is_threaded == 0)
        return (0);
    id = pthread_self();
    /* horrible but preserves compat, see warning above */
    memcpy(&ret, &id, sizeof(ret));
    return (ret);
#elif defined HAVE_WIN32_THREADS
    return GetCurrentThreadId();
#elif defined HAVE_BEOS_THREADS
    return find_thread(NULL);
#else
    return ((int) 0);
#endif
}

/**
 * xmlIsMainThread:
 *
 * xmlIsMainThread() check whether the current thread is the main thread.
 *
 * Returns 1 if the current thread is the main thread, 0 otherwise
 */
int
xmlIsMainThread(void)
{
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded == -1)
        xmlInitThreads();
    if (libxml_is_threaded == 0)
        return (1);
    pthread_once(&once_control, xmlOnceInit);
#elif defined HAVE_WIN32_THREADS
    xmlOnceInit();
#elif defined HAVE_BEOS_THREADS
    xmlOnceInit();
#endif

#ifdef DEBUG_THREADS
    xmlGenericError(xmlGenericErrorContext, "xmlIsMainThread()\n");
#endif
#ifdef HAVE_PTHREAD_H
    return (pthread_equal(mainthread,pthread_self()));
#elif defined HAVE_WIN32_THREADS
    return (mainthread == GetCurrentThreadId());
#elif defined HAVE_BEOS_THREADS
    return (mainthread == find_thread(NULL));
#else
    return (1);
#endif
}

/**
 * xmlLockLibrary:
 *
 * xmlLockLibrary() is used to take out a re-entrant lock on the libxml2
 * library.
 */
void
xmlLockLibrary(void)
{
#ifdef DEBUG_THREADS
    xmlGenericError(xmlGenericErrorContext, "xmlLockLibrary()\n");
#endif
    xmlRMutexLock(xmlLibraryLock);
}

/**
 * xmlUnlockLibrary:
 *
 * xmlUnlockLibrary() is used to release a re-entrant lock on the libxml2
 * library.
 */
void
xmlUnlockLibrary(void)
{
#ifdef DEBUG_THREADS
    xmlGenericError(xmlGenericErrorContext, "xmlUnlockLibrary()\n");
#endif
    xmlRMutexUnlock(xmlLibraryLock);
}

/**
 * xmlInitThreads:
 *
 * xmlInitThreads() is used to to initialize all the thread related
 * data of the libxml2 library.
 */
void
xmlInitThreads(void)
{
#ifdef HAVE_PTHREAD_H
    if (libxml_is_threaded == -1) {
        if ((pthread_once != NULL) &&
            (pthread_getspecific != NULL) &&
            (pthread_setspecific != NULL) &&
            (pthread_key_create != NULL) &&
            (pthread_key_delete != NULL) &&
            (pthread_mutex_init != NULL) &&
            (pthread_mutex_destroy != NULL) &&
            (pthread_mutex_lock != NULL) &&
            (pthread_mutex_unlock != NULL) &&
            (pthread_cond_init != NULL) &&
            (pthread_cond_destroy != NULL) &&
            (pthread_cond_wait != NULL) &&
            (pthread_equal != NULL) &&
            (pthread_self != NULL) &&
            (pthread_cond_signal != NULL)) {
            libxml_is_threaded = 1;

/* fprintf(stderr, "Running multithreaded\n"); */
        } else {

/* fprintf(stderr, "Running without multithread\n"); */
            libxml_is_threaded = 0;
        }
    }
#elif defined(HAVE_WIN32_THREADS) && !defined(HAVE_COMPILER_TLS) && (!defined(LIBXML_STATIC) || defined(LIBXML_STATIC_FOR_DLL))
    InitializeCriticalSection(&cleanup_helpers_cs);
#endif
}

/**
 * xmlCleanupThreads:
 *
 * xmlCleanupThreads() is used to to cleanup all the thread related
 * data of the libxml2 library once processing has ended.
 *
 * WARNING: if your application is multithreaded or has plugin support
 *          calling this may crash the application if another thread or
 *          a plugin is still using libxml2. It's sometimes very hard to
 *          guess if libxml2 is in use in the application, some libraries
 *          or plugins may use it without notice. In case of doubt abstain
 *          from calling this function or do it just before calling exit()
 *          to avoid leak reports from valgrind !
 */
void
xmlCleanupThreads(void)
{
#ifdef DEBUG_THREADS
    xmlGenericError(xmlGenericErrorContext, "xmlCleanupThreads()\n");
#endif
#ifdef HAVE_PTHREAD_H
    if ((libxml_is_threaded)  && (pthread_key_delete != NULL))
        pthread_key_delete(globalkey);
#elif defined(HAVE_WIN32_THREADS) && !defined(HAVE_COMPILER_TLS) && (!defined(LIBXML_STATIC) || defined(LIBXML_STATIC_FOR_DLL))
    if (globalkey != TLS_OUT_OF_INDEXES) {
        xmlGlobalStateCleanupHelperParams *p;

        EnterCriticalSection(&cleanup_helpers_cs);
        p = cleanup_helpers_head;
        while (p != NULL) {
            xmlGlobalStateCleanupHelperParams *temp = p;

            p = p->next;
            xmlFreeGlobalState(temp->memory);
            free(temp);
        }
        cleanup_helpers_head = 0;
        LeaveCriticalSection(&cleanup_helpers_cs);
        TlsFree(globalkey);
        globalkey = TLS_OUT_OF_INDEXES;
    }
    DeleteCriticalSection(&cleanup_helpers_cs);
#endif
}

#ifdef LIBXML_THREAD_ENABLED

/**
 * xmlOnceInit
 *
 * xmlOnceInit() is used to initialize the value of mainthread for use
 * in other routines. This function should only be called using
 * pthread_once() in association with the once_control variable to ensure
 * that the function is only called once. See man pthread_once for more
 * details.
 */
static void
xmlOnceInit(void)
{
#ifdef HAVE_PTHREAD_H
    (void) pthread_key_create(&globalkey, xmlFreeGlobalState);
    mainthread = pthread_self();
#elif defined(HAVE_WIN32_THREADS)
    if (!run_once.done) {
        if (InterlockedIncrement(&run_once.control) == 1) {
#if !defined(HAVE_COMPILER_TLS)
            globalkey = TlsAlloc();
#endif
            mainthread = GetCurrentThreadId();
            run_once.done = 1;
        } else {
            /* Another thread is working; give up our slice and
             * wait until they're done. */
            while (!run_once.done)
                Sleep(0);
        }
    }
#elif defined HAVE_BEOS_THREADS
    if (atomic_add(&run_once_init, 1) == 0) {
        globalkey = tls_allocate();
        tls_set(globalkey, NULL);
        mainthread = find_thread(NULL);
    } else
        atomic_add(&run_once_init, -1);
#endif
}
#endif

/**
 * DllMain:
 * @hinstDLL: handle to DLL instance
 * @fdwReason: Reason code for entry
 * @lpvReserved: generic pointer (depends upon reason code)
 *
 * Entry point for Windows library. It is being used to free thread-specific
 * storage.
 *
 * Returns TRUE always
 */
#ifdef HAVE_PTHREAD_H
#elif defined(HAVE_WIN32_THREADS) && !defined(HAVE_COMPILER_TLS) && (!defined(LIBXML_STATIC) || defined(LIBXML_STATIC_FOR_DLL))
#if defined(LIBXML_STATIC_FOR_DLL)
BOOL XMLCALL
xmlDllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
#else
BOOL WINAPI
DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
#endif
{
    switch (fdwReason) {
        case DLL_THREAD_DETACH:
            if (globalkey != TLS_OUT_OF_INDEXES) {
                xmlGlobalState *globalval = NULL;
                xmlGlobalStateCleanupHelperParams *p =
                    (xmlGlobalStateCleanupHelperParams *)
                    TlsGetValue(globalkey);
                globalval = (xmlGlobalState *) (p ? p->memory : NULL);
                if (globalval) {
                    xmlFreeGlobalState(globalval);
                    TlsSetValue(globalkey, NULL);
                }
                if (p) {
                    EnterCriticalSection(&cleanup_helpers_cs);
                    if (p == cleanup_helpers_head)
                        cleanup_helpers_head = p->next;
                    else
                        p->prev->next = p->next;
                    if (p->next != NULL)
                        p->next->prev = p->prev;
                    LeaveCriticalSection(&cleanup_helpers_cs);
                    free(p);
                }
            }
            break;
    }
    return TRUE;
}
#endif
#define bottom_threads
#include "elfgcchack.h"