1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2024-12-24 21:33:51 +03:00
libxml2/schematron.c
Rosen Penev 217e9b7af2 clang-tidy: don't return in void functions
Found with readability-redundant-control-flow

Signed-off-by: Rosen Penev <rosenp@gmail.com>
2024-06-20 20:37:34 +00:00

2093 lines
61 KiB
C

/*
* 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; \
}
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)
{
if (ctxt != NULL)
ctxt->nberrors++;
xmlRaiseMemoryError(NULL, NULL, NULL, XML_FROM_SCHEMASP, NULL);
}
/**
* 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;
int res;
if (ctxt != NULL) {
ctxt->nberrors++;
channel = ctxt->error;
data = ctxt->userData;
schannel = ctxt->serror;
}
if ((channel == NULL) && (schannel == NULL)) {
channel = xmlGenericError;
data = xmlGenericErrorContext;
}
res = __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);
if (res < 0)
xmlSchematronPErrMemory(ctxt);
}
/**
* xmlSchematronVTypeErrMemory:
* @node: a context node
* @extra: extra information
*
* Handle an out of memory condition
*/
static void
xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt)
{
if (ctxt != NULL) {
ctxt->nberrors++;
ctxt->err = XML_SCHEMAV_INTERNAL;
}
xmlRaiseMemoryError(NULL, NULL, NULL, XML_FROM_SCHEMASV, NULL);
}
/**
* xmlSchematronVErr:
* @ctxt: the parsing context
* @node: the context node
* @error: the error code
* @msg: the error message
* @str1: extra data
* @str2: extra data
*
* Handle a validation error
*/
static void LIBXML_ATTR_FORMAT(3,0)
xmlSchematronVErr(xmlSchematronValidCtxtPtr ctxt, int error,
const char *msg, const xmlChar * str1)
{
xmlGenericErrorFunc channel = NULL;
xmlStructuredErrorFunc schannel = NULL;
void *data = NULL;
int res;
if (ctxt != NULL) {
ctxt->nberrors++;
channel = ctxt->error;
data = ctxt->userData;
schannel = ctxt->serror;
}
if ((channel == NULL) && (schannel == NULL)) {
channel = xmlGenericError;
data = xmlGenericErrorContext;
}
res = __xmlRaiseError(schannel, channel, data, ctxt, NULL,
XML_FROM_SCHEMASV, error, XML_ERR_ERROR, NULL, 0,
(const char *) str1, NULL, NULL, 0, 0,
msg, str1);
if (res < 0)
xmlSchematronVErrMemory(ctxt);
}
/************************************************************************
* *
* 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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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;
}
}
/**
* 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);
}
}
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:
xmlSchematronVErr(ctxt, XML_ERR_INTERNAL_ERROR,
"Unsupported XPATH Type\n", NULL);
}
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;
xmlGenericErrorFunc channel;
void *data;
int res;
schannel = ctxt->serror;
channel = ctxt->error;
data = ctxt->userData;
if ((channel == NULL) && (schannel == NULL)) {
channel = xmlGenericError;
data = xmlGenericErrorContext;
}
res = __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);
if (res < 0)
xmlSchematronVErrMemory(ctxt);
} 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);
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);
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:
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(xmlSchematronValidCtxtPtr vctxt,
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) {
xmlSchematronVErr(vctxt, XML_ERR_INTERNAL_ERROR,
"Evaluation of compiled expression failed\n",
NULL);
return -1;
}
if(xmlXPathRegisterVariableNS(ctxt, let->name, NULL, let_eval)) {
xmlSchematronVErr(vctxt, XML_ERR_INTERNAL_ERROR,
"Registering a let variable failed\n", NULL);
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(xmlSchematronValidCtxtPtr vctxt,
xmlXPathContextPtr ctxt,
xmlSchematronLetPtr let)
{
while (let != NULL) {
if (xmlXPathRegisterVariableNS(ctxt, let->name, NULL, NULL)) {
xmlSchematronVErr(vctxt, XML_ERR_INTERNAL_ERROR,
"Unregistering a let variable failed\n", NULL);
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, 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, 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, ctxt->xctxt,
rule->lets, instance, cur);
while (test != NULL) {
xmlSchematronRunTest(ctxt, test, instance, cur, pattern);
test = test->next;
}
xmlSchematronUnregisterVariables(ctxt, 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 */