1
0
mirror of https://gitlab.gnome.org/GNOME/libxml2.git synced 2024-10-26 12:25:09 +03:00
libxml2/schematron.c
Nick Wellnhofer 669bd34993 xpointer: Remove support for XPointer locations
The latest spec for what it essentially an XPath extension seems to be
this working draft from 2002:

    https://www.w3.org/TR/xptr-xpointer/

The xpointer() scheme is listed as "being reviewed" in the XPointer
registry since at least 2006. libxml2 seems to be the only modern
software that tries to implement this spec, but the code has many bugs
and quality issues.

If you configure --with-legacy, old symbols are retained for ABI
compatibility.
2024-06-12 18:20:01 +02:00

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