Skip to content
Snippets Groups Projects
schematron.c 61.32 KiB
/*
 * 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 <stdlib.h>
#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>

#include "private/error.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;

/**
 * _xmlSchematronLet:
 *
 * A Schematron let variable
 */
typedef struct _xmlSchematronLet xmlSchematronLet;
typedef xmlSchematronLet *xmlSchematronLetPtr;
struct _xmlSchematronLet {
    xmlSchematronLetPtr next; /* the next let variable in the list */
    xmlChar *name;            /* the name of the variable */
    xmlXPathCompExprPtr comp; /* the compiled expression */
};

/**
 * _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 */
    xmlSchematronLetPtr lets;   /* the list of let variables */
};

/**
 * _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 dictionary 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 */
#ifdef LIBXML_OUTPUT_ENABLED
    xmlOutputWriteCallback iowrite; /* if using XML_SCHEMATRON_OUT_IO */
    xmlOutputCloseCallback  ioclose;
#endif
    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;            /* dictionary 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 information
 *
 * 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 LIBXML_ATTR_FORMAT(4,0)
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 information
 *
 * 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;
    }
}

/**
 * xmlSchematronFreeLets:
 * @lets:  a list of let variables
 *
 * Free a list of let variables.
 */
static void
xmlSchematronFreeLets(xmlSchematronLetPtr lets) {
    xmlSchematronLetPtr next;

    while (lets != NULL) {
        next = lets->next;
        if (lets->name != NULL)
            xmlFree(lets->name);
        if (lets->comp != NULL)
            xmlXPathFreeCompExpr(lets->comp);
        xmlFree(lets);
        lets = 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;
    ret->lets = 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);
        if (rules->lets != NULL)
            xmlSchematronFreeLets(rules->lets);
        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;

}

/**
 * xmlSchematronParseTestReportMsg:
 * @ctxt:  the schema parser context
 * @con:  the assert or report node
 *
 * Format the message content of the assert or report test
 */
static void
xmlSchematronParseTestReportMsg(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr con)
{
    xmlNodePtr child;
    xmlXPathCompExprPtr comp;

    child = con->children;
    while (child != NULL) {
        if ((child->type == XML_TEXT_NODE) ||
            (child->type == XML_CDATA_SECTION_NODE))
            /* Do Nothing */
            {}
        else if (IS_SCHEMATRON(child, "name")) {
            /* Do Nothing */
        } else if (IS_SCHEMATRON(child, "value-of")) {
            xmlChar *select;

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

            if (select == NULL) {
                xmlSchematronPErr(ctxt, child,
                                  XML_SCHEMAV_ATTRINVALID,
                                  "value-of has no select attribute",
                                  NULL, NULL);
            } else {
                /*
                 * try first to compile the test expression
                 */
                comp = xmlXPathCtxtCompile(ctxt->xctxt, select);
                if (comp == NULL) {
                    xmlSchematronPErr(ctxt, child,
                                      XML_SCHEMAV_ATTRINVALID,
                                      "Failed to compile select expression %s",
                                      select, NULL);
                }
                xmlXPathFreeCompExpr(comp);
            }
            xmlFree(select);
        }
        child = child->next;
        continue;
    }
}

/**
 * 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;
    xmlChar *name;
    xmlChar *value;
    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, "let")) {
            xmlXPathCompExprPtr var_comp;
            xmlSchematronLetPtr let;

            name = xmlGetNoNsProp(cur, BAD_CAST "name");
            if (name == NULL) {
                xmlSchematronPErr(ctxt, cur,
                                  XML_SCHEMAP_NOROOT,
                                  "let has no name attribute",
                                  NULL, NULL);
                return;
            } else if (name[0] == 0) {
                xmlSchematronPErr(ctxt, cur,
                                  XML_SCHEMAP_NOROOT,
                                  "let has an empty name attribute",
                                  NULL, NULL);
                xmlFree(name);
                return;
            }
            value = xmlGetNoNsProp(cur, BAD_CAST "value");
            if (value == NULL) {
                xmlSchematronPErr(ctxt, cur,
                                  XML_SCHEMAP_NOROOT,
                                  "let has no value attribute",
                                  NULL, NULL);
                return;
            } else if (value[0] == 0) {
                xmlSchematronPErr(ctxt, cur,
                                  XML_SCHEMAP_NOROOT,
                                  "let has an empty value attribute",
                                  NULL, NULL);
                xmlFree(value);
                return;
            }

            var_comp = xmlXPathCtxtCompile(ctxt->xctxt, value);
            if (var_comp == NULL) {
                xmlSchematronPErr(ctxt, cur,
                                  XML_SCHEMAP_NOROOT,
                                  "Failed to compile let expression %s",
                                  value, NULL);
                return;
            }

            let = (xmlSchematronLetPtr) malloc(sizeof(xmlSchematronLet));
            let->name = name;
            let->comp = var_comp;
            let->next = NULL;

            /* add new let variable to the beginning of the list */
            if (ruleptr->lets != NULL) {
                let->next = ruleptr->lets;
            }
            ruleptr->lets = let;

            xmlFree(value);
        } else 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 {
                xmlSchematronParseTestReportMsg(ctxt, cur);
                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 {
                xmlSchematronParseTestReportMsg(ctxt, cur);
                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 Schema structure 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 the caller
 */
static xmlChar *
xmlSchematronFormatReport(xmlSchematronValidCtxtPtr ctxt,
                          xmlNodePtr test, xmlNodePtr cur) {
    xmlChar *ret = NULL;
    xmlNodePtr child, node;
    xmlXPathCompExprPtr comp;

    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 if (IS_SCHEMATRON(child, "value-of")) {
            xmlChar *select;
            xmlXPathObjectPtr eval;

            select = xmlGetNoNsProp(child, BAD_CAST "select");
            comp = xmlXPathCtxtCompile(ctxt->xctxt, select);
            eval = xmlXPathCompiledEval(comp, ctxt->xctxt);

            switch (eval->type) {
            case XPATH_NODESET: {
                int indx;
                xmlChar *spacer = BAD_CAST " ";

                if (eval->nodesetval) {
                    for (indx = 0; indx < eval->nodesetval->nodeNr; indx++) {
                        if (indx > 0)
                            ret = xmlStrcat(ret, spacer);
                        ret = xmlStrcat(ret, eval->nodesetval->nodeTab[indx]->name);
                    }
                } else {
                    xmlGenericError(xmlGenericErrorContext,
                                    "Empty node set\n");
                }
                break;
            }
            case XPATH_BOOLEAN: {
                const char *str = eval->boolval ? "True" : "False";
                ret = xmlStrcat(ret, BAD_CAST str);
                break;
            }
            case XPATH_NUMBER: {
                xmlChar *buf;
                int size;

                size = snprintf(NULL, 0, "%0g", eval->floatval);
                buf = (xmlChar *) xmlMalloc(size + 1);
                if (buf != NULL) {
                    snprintf((char *) buf, size + 1, "%0g", eval->floatval);
                    ret = xmlStrcat(ret, buf);
                    xmlFree(buf);
                }
                break;
            }
            case XPATH_STRING:
                ret = xmlStrcat(ret, eval->stringval);
                break;
            default:
                xmlGenericError(xmlGenericErrorContext,
                                "Unsupported XPATH Type: %d\n", eval->type);
            }
            xmlXPathFreeObject(eval);
            xmlXPathFreeCompExpr(comp);
            xmlFree(select);
        } 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 instance 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:
#ifdef LIBXML_XPTR_LOCS_ENABLED
            case XPATH_POINT:
            case XPATH_RANGE:
            case XPATH_LOCATIONSET:
#endif
            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);
}

/**
 * xmlSchematronRegisterVariables:
 * @ctxt:  the schema validation context
 * @let:  the list of let variables
 * @instance:  the document instance tree
 * @cur:  the current node
 *
 * Registers a list of let variables to the current context of @cur
 *
 * Returns -1 in case of errors, otherwise 0
 */
static int
xmlSchematronRegisterVariables(xmlXPathContextPtr ctxt, xmlSchematronLetPtr let,
                               xmlDocPtr instance, xmlNodePtr cur)
{
    xmlXPathObjectPtr let_eval;

    ctxt->doc = instance;
    ctxt->node = cur;
    while (let != NULL) {
        let_eval = xmlXPathCompiledEval(let->comp, ctxt);
        if (let_eval == NULL) {
            xmlGenericError(xmlGenericErrorContext,
                            "Evaluation of compiled expression failed\n");
            return -1;
        }
        if(xmlXPathRegisterVariableNS(ctxt, let->name, NULL, let_eval)) {
            xmlGenericError(xmlGenericErrorContext,
                            "Registering a let variable failed\n");
            return -1;
        }
        let = let->next;
    }
    return 0;
}

/**
 * xmlSchematronUnregisterVariables:
 * @ctxt:  the schema validation context
 * @let:  the list of let variables
 *
 * Unregisters a list of let variables from the context
 *
 * Returns -1 in case of errors, otherwise 0
 */
static int
xmlSchematronUnregisterVariables(xmlXPathContextPtr ctxt, xmlSchematronLetPtr let)
{
    while (let != NULL) {
        if (xmlXPathRegisterVariableNS(ctxt, let->name, NULL, NULL)) {
            xmlGenericError(xmlGenericErrorContext,
                            "Unregistering a let variable failed\n");
            return -1;
        }
        let = let->next;
    }
    return 0;
}

/**
 * xmlSchematronValidateDoc:
 * @ctxt:  the schema validation context
 * @instance:  the document instance 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;
                    if (xmlSchematronRegisterVariables(ctxt->xctxt, rule->lets, instance, cur))
                        return -1;

                    while (test != NULL) {
                        xmlSchematronRunTest(ctxt, test, instance, cur, (xmlSchematronPatternPtr)rule->pattern);
                        test = test->next;
                    }

                    if (xmlSchematronUnregisterVariables(ctxt->xctxt, rule->lets))
                        return -1;

                }
                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;
                        xmlSchematronRegisterVariables(ctxt->xctxt, rule->lets,
                                                       instance, cur);

                        while (test != NULL) {
                            xmlSchematronRunTest(ctxt, test, instance, cur, pattern);
                            test = test->next;
                        }

                        xmlSchematronUnregisterVariables(ctxt->xctxt, rule->lets);
                    }
                    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();

    return (0);
}
#endif

#endif /* LIBXML_SCHEMATRON_ENABLED */