1788 lines
47 KiB
C
1788 lines
47 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 <string.h>
|
|
#include <libxml/parser.h>
|
|
#include <libxml/tree.h>
|
|
#include <libxml/uri.h>
|
|
#include <libxml/xpath.h>
|
|
#include <libxml/xpathInternals.h>
|
|
#include <libxml/pattern.h>
|
|
#include <libxml/schematron.h>
|
|
|
|
#define SCHEMATRON_PARSE_OPTIONS XML_PARSE_NOENT
|
|
|
|
#define SCT_OLD_NS BAD_CAST "http://www.ascc.net/xml/schematron"
|
|
|
|
#define XML_SCHEMATRON_NS BAD_CAST "http://purl.oclc.org/dsdl/schematron"
|
|
|
|
|
|
static const xmlChar *xmlSchematronNs = XML_SCHEMATRON_NS;
|
|
static const xmlChar *xmlOldSchematronNs = SCT_OLD_NS;
|
|
|
|
#define IS_SCHEMATRON(node, elem) \
|
|
((node != NULL) && (node->type == XML_ELEMENT_NODE ) && \
|
|
(node->ns != NULL) && \
|
|
(xmlStrEqual(node->name, (const xmlChar *) elem)) && \
|
|
((xmlStrEqual(node->ns->href, xmlSchematronNs)) || \
|
|
(xmlStrEqual(node->ns->href, xmlOldSchematronNs))))
|
|
|
|
#define NEXT_SCHEMATRON(node) \
|
|
while (node != NULL) { \
|
|
if ((node->type == XML_ELEMENT_NODE ) && (node->ns != NULL) && \
|
|
((xmlStrEqual(node->ns->href, xmlSchematronNs)) || \
|
|
(xmlStrEqual(node->ns->href, xmlOldSchematronNs)))) \
|
|
break; \
|
|
node = node->next; \
|
|
}
|
|
|
|
/**
|
|
* TODO:
|
|
*
|
|
* macro to flag unimplemented blocks
|
|
*/
|
|
#define TODO \
|
|
xmlGenericError(xmlGenericErrorContext, \
|
|
"Unimplemented block at %s:%d\n", \
|
|
__FILE__, __LINE__);
|
|
|
|
typedef enum {
|
|
XML_SCHEMATRON_ASSERT=1,
|
|
XML_SCHEMATRON_REPORT=2
|
|
} xmlSchematronTestType;
|
|
|
|
/**
|
|
* _xmlSchematronTest:
|
|
*
|
|
* A Schematrons test, either an assert or a report
|
|
*/
|
|
typedef struct _xmlSchematronTest xmlSchematronTest;
|
|
typedef xmlSchematronTest *xmlSchematronTestPtr;
|
|
struct _xmlSchematronTest {
|
|
xmlSchematronTestPtr next; /* the next test in the list */
|
|
xmlSchematronTestType type; /* the test type */
|
|
xmlNodePtr node; /* the node in the tree */
|
|
xmlChar *test; /* the expression to test */
|
|
xmlXPathCompExprPtr comp; /* the compiled expression */
|
|
xmlChar *report; /* the message to report */
|
|
};
|
|
|
|
/**
|
|
* _xmlSchematronRule:
|
|
*
|
|
* A Schematrons rule
|
|
*/
|
|
typedef struct _xmlSchematronRule xmlSchematronRule;
|
|
typedef xmlSchematronRule *xmlSchematronRulePtr;
|
|
struct _xmlSchematronRule {
|
|
xmlSchematronRulePtr next; /* the next rule in the list */
|
|
xmlSchematronRulePtr patnext;/* the next rule in the pattern list */
|
|
xmlNodePtr node; /* the node in the tree */
|
|
xmlChar *context; /* the context evaluation rule */
|
|
xmlSchematronTestPtr tests; /* the list of tests */
|
|
xmlPatternPtr pattern; /* the compiled pattern associated */
|
|
xmlChar *report; /* the message to report */
|
|
};
|
|
|
|
/**
|
|
* _xmlSchematronPattern:
|
|
*
|
|
* A Schematrons pattern
|
|
*/
|
|
typedef struct _xmlSchematronPattern xmlSchematronPattern;
|
|
typedef xmlSchematronPattern *xmlSchematronPatternPtr;
|
|
struct _xmlSchematronPattern {
|
|
xmlSchematronPatternPtr next;/* the next pattern in the list */
|
|
xmlSchematronRulePtr rules; /* the list of rules */
|
|
xmlChar *name; /* the name of the pattern */
|
|
};
|
|
|
|
/**
|
|
* _xmlSchematron:
|
|
*
|
|
* A Schematrons definition
|
|
*/
|
|
struct _xmlSchematron {
|
|
const xmlChar *name; /* schema name */
|
|
int preserve; /* was the document passed by the user */
|
|
xmlDocPtr doc; /* pointer to the parsed document */
|
|
int flags; /* specific to this schematron */
|
|
|
|
void *_private; /* unused by the library */
|
|
xmlDictPtr dict; /* the 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 informations
|
|
*
|
|
* Handle an out of memory condition
|
|
*/
|
|
static void
|
|
xmlSchematronPErrMemory(xmlSchematronParserCtxtPtr ctxt,
|
|
const char *extra, xmlNodePtr node)
|
|
{
|
|
if (ctxt != NULL)
|
|
ctxt->nberrors++;
|
|
__xmlSimpleError(XML_FROM_SCHEMASP, XML_ERR_NO_MEMORY, node, NULL,
|
|
extra);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronPErr:
|
|
* @ctxt: the parsing context
|
|
* @node: the context node
|
|
* @error: the error code
|
|
* @msg: the error message
|
|
* @str1: extra data
|
|
* @str2: extra data
|
|
*
|
|
* Handle a parser error
|
|
*/
|
|
static void LIBXML_ATTR_FORMAT(4,0)
|
|
xmlSchematronPErr(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr node, int error,
|
|
const char *msg, const xmlChar * str1, const xmlChar * str2)
|
|
{
|
|
xmlGenericErrorFunc channel = NULL;
|
|
xmlStructuredErrorFunc schannel = NULL;
|
|
void *data = NULL;
|
|
|
|
if (ctxt != NULL) {
|
|
ctxt->nberrors++;
|
|
channel = ctxt->error;
|
|
data = ctxt->userData;
|
|
schannel = ctxt->serror;
|
|
}
|
|
__xmlRaiseError(schannel, channel, data, ctxt, node, XML_FROM_SCHEMASP,
|
|
error, XML_ERR_ERROR, NULL, 0,
|
|
(const char *) str1, (const char *) str2, NULL, 0, 0,
|
|
msg, str1, str2);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronVTypeErrMemory:
|
|
* @node: a context node
|
|
* @extra: extra informations
|
|
*
|
|
* Handle an out of memory condition
|
|
*/
|
|
static void
|
|
xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt,
|
|
const char *extra, xmlNodePtr node)
|
|
{
|
|
if (ctxt != NULL) {
|
|
ctxt->nberrors++;
|
|
ctxt->err = XML_SCHEMAV_INTERNAL;
|
|
}
|
|
__xmlSimpleError(XML_FROM_SCHEMASV, XML_ERR_NO_MEMORY, node, NULL,
|
|
extra);
|
|
}
|
|
|
|
/************************************************************************
|
|
* *
|
|
* Parsing and compilation of the Schematrontrons *
|
|
* *
|
|
************************************************************************/
|
|
|
|
/**
|
|
* xmlSchematronAddTest:
|
|
* @ctxt: the schema parsing context
|
|
* @type: the type of test
|
|
* @rule: the parent rule
|
|
* @node: the node hosting the test
|
|
* @test: the associated test
|
|
* @report: the associated report string
|
|
*
|
|
* Add a test to a schematron
|
|
*
|
|
* Returns the new pointer or NULL in case of error
|
|
*/
|
|
static xmlSchematronTestPtr
|
|
xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt,
|
|
xmlSchematronTestType type,
|
|
xmlSchematronRulePtr rule,
|
|
xmlNodePtr node, xmlChar *test, xmlChar *report)
|
|
{
|
|
xmlSchematronTestPtr ret;
|
|
xmlXPathCompExprPtr comp;
|
|
|
|
if ((ctxt == NULL) || (rule == NULL) || (node == NULL) ||
|
|
(test == NULL))
|
|
return(NULL);
|
|
|
|
/*
|
|
* try first to compile the test expression
|
|
*/
|
|
comp = xmlXPathCtxtCompile(ctxt->xctxt, test);
|
|
if (comp == NULL) {
|
|
xmlSchematronPErr(ctxt, node,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Failed to compile test expression %s",
|
|
test, NULL);
|
|
return(NULL);
|
|
}
|
|
|
|
ret = (xmlSchematronTestPtr) xmlMalloc(sizeof(xmlSchematronTest));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(ctxt, "allocating schema test", node);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronTest));
|
|
ret->type = type;
|
|
ret->node = node;
|
|
ret->test = test;
|
|
ret->comp = comp;
|
|
ret->report = report;
|
|
ret->next = NULL;
|
|
if (rule->tests == NULL) {
|
|
rule->tests = ret;
|
|
} else {
|
|
xmlSchematronTestPtr prev = rule->tests;
|
|
|
|
while (prev->next != NULL)
|
|
prev = prev->next;
|
|
prev->next = ret;
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFreeTests:
|
|
* @tests: a list of tests
|
|
*
|
|
* Free a list of tests.
|
|
*/
|
|
static void
|
|
xmlSchematronFreeTests(xmlSchematronTestPtr tests) {
|
|
xmlSchematronTestPtr next;
|
|
|
|
while (tests != NULL) {
|
|
next = tests->next;
|
|
if (tests->test != NULL)
|
|
xmlFree(tests->test);
|
|
if (tests->comp != NULL)
|
|
xmlXPathFreeCompExpr(tests->comp);
|
|
if (tests->report != NULL)
|
|
xmlFree(tests->report);
|
|
xmlFree(tests);
|
|
tests = next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronAddRule:
|
|
* @ctxt: the schema parsing context
|
|
* @schema: a schema structure
|
|
* @node: the node hosting the rule
|
|
* @context: the associated context string
|
|
* @report: the associated report string
|
|
*
|
|
* Add a rule to a schematron
|
|
*
|
|
* Returns the new pointer or NULL in case of error
|
|
*/
|
|
static xmlSchematronRulePtr
|
|
xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt, xmlSchematronPtr schema,
|
|
xmlSchematronPatternPtr pat, xmlNodePtr node,
|
|
xmlChar *context, xmlChar *report)
|
|
{
|
|
xmlSchematronRulePtr ret;
|
|
xmlPatternPtr pattern;
|
|
|
|
if ((ctxt == NULL) || (schema == NULL) || (node == NULL) ||
|
|
(context == NULL))
|
|
return(NULL);
|
|
|
|
/*
|
|
* Try first to compile the pattern
|
|
*/
|
|
pattern = xmlPatterncompile(context, ctxt->dict, XML_PATTERN_XPATH,
|
|
ctxt->namespaces);
|
|
if (pattern == NULL) {
|
|
xmlSchematronPErr(ctxt, node,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Failed to compile context expression %s",
|
|
context, NULL);
|
|
}
|
|
|
|
ret = (xmlSchematronRulePtr) xmlMalloc(sizeof(xmlSchematronRule));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(ctxt, "allocating schema rule", node);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronRule));
|
|
ret->node = node;
|
|
ret->context = context;
|
|
ret->pattern = pattern;
|
|
ret->report = report;
|
|
ret->next = NULL;
|
|
if (schema->rules == NULL) {
|
|
schema->rules = ret;
|
|
} else {
|
|
xmlSchematronRulePtr prev = schema->rules;
|
|
|
|
while (prev->next != NULL)
|
|
prev = prev->next;
|
|
prev->next = ret;
|
|
}
|
|
ret->patnext = NULL;
|
|
if (pat->rules == NULL) {
|
|
pat->rules = ret;
|
|
} else {
|
|
xmlSchematronRulePtr prev = pat->rules;
|
|
|
|
while (prev->patnext != NULL)
|
|
prev = prev->patnext;
|
|
prev->patnext = ret;
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFreeRules:
|
|
* @rules: a list of rules
|
|
*
|
|
* Free a list of rules.
|
|
*/
|
|
static void
|
|
xmlSchematronFreeRules(xmlSchematronRulePtr rules) {
|
|
xmlSchematronRulePtr next;
|
|
|
|
while (rules != NULL) {
|
|
next = rules->next;
|
|
if (rules->tests)
|
|
xmlSchematronFreeTests(rules->tests);
|
|
if (rules->context != NULL)
|
|
xmlFree(rules->context);
|
|
if (rules->pattern)
|
|
xmlFreePattern(rules->pattern);
|
|
if (rules->report != NULL)
|
|
xmlFree(rules->report);
|
|
xmlFree(rules);
|
|
rules = next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronAddPattern:
|
|
* @ctxt: the schema parsing context
|
|
* @schema: a schema structure
|
|
* @node: the node hosting the pattern
|
|
* @id: the id or name of the pattern
|
|
*
|
|
* Add a pattern to a schematron
|
|
*
|
|
* Returns the new pointer or NULL in case of error
|
|
*/
|
|
static xmlSchematronPatternPtr
|
|
xmlSchematronAddPattern(xmlSchematronParserCtxtPtr ctxt,
|
|
xmlSchematronPtr schema, xmlNodePtr node, xmlChar *name)
|
|
{
|
|
xmlSchematronPatternPtr ret;
|
|
|
|
if ((ctxt == NULL) || (schema == NULL) || (node == NULL) || (name == NULL))
|
|
return(NULL);
|
|
|
|
ret = (xmlSchematronPatternPtr) xmlMalloc(sizeof(xmlSchematronPattern));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(ctxt, "allocating schema pattern", node);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronPattern));
|
|
ret->name = name;
|
|
ret->next = NULL;
|
|
if (schema->patterns == NULL) {
|
|
schema->patterns = ret;
|
|
} else {
|
|
xmlSchematronPatternPtr prev = schema->patterns;
|
|
|
|
while (prev->next != NULL)
|
|
prev = prev->next;
|
|
prev->next = ret;
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFreePatterns:
|
|
* @patterns: a list of patterns
|
|
*
|
|
* Free a list of patterns.
|
|
*/
|
|
static void
|
|
xmlSchematronFreePatterns(xmlSchematronPatternPtr patterns) {
|
|
xmlSchematronPatternPtr next;
|
|
|
|
while (patterns != NULL) {
|
|
next = patterns->next;
|
|
if (patterns->name != NULL)
|
|
xmlFree(patterns->name);
|
|
xmlFree(patterns);
|
|
patterns = next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronNewSchematron:
|
|
* @ctxt: a schema validation context
|
|
*
|
|
* Allocate a new Schematron structure.
|
|
*
|
|
* Returns the newly allocated structure or NULL in case or error
|
|
*/
|
|
static xmlSchematronPtr
|
|
xmlSchematronNewSchematron(xmlSchematronParserCtxtPtr ctxt)
|
|
{
|
|
xmlSchematronPtr ret;
|
|
|
|
ret = (xmlSchematronPtr) xmlMalloc(sizeof(xmlSchematron));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(ctxt, "allocating schema", NULL);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematron));
|
|
ret->dict = ctxt->dict;
|
|
xmlDictReference(ret->dict);
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFree:
|
|
* @schema: a schema structure
|
|
*
|
|
* Deallocate a Schematron structure.
|
|
*/
|
|
void
|
|
xmlSchematronFree(xmlSchematronPtr schema)
|
|
{
|
|
if (schema == NULL)
|
|
return;
|
|
|
|
if ((schema->doc != NULL) && (!(schema->preserve)))
|
|
xmlFreeDoc(schema->doc);
|
|
|
|
if (schema->namespaces != NULL)
|
|
xmlFree((char **) schema->namespaces);
|
|
|
|
xmlSchematronFreeRules(schema->rules);
|
|
xmlSchematronFreePatterns(schema->patterns);
|
|
xmlDictFree(schema->dict);
|
|
xmlFree(schema);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronNewParserCtxt:
|
|
* @URL: the location of the schema
|
|
*
|
|
* Create an XML Schematrons parse context for that file/resource expected
|
|
* to contain an XML Schematrons file.
|
|
*
|
|
* Returns the parser context or NULL in case of error
|
|
*/
|
|
xmlSchematronParserCtxtPtr
|
|
xmlSchematronNewParserCtxt(const char *URL)
|
|
{
|
|
xmlSchematronParserCtxtPtr ret;
|
|
|
|
if (URL == NULL)
|
|
return (NULL);
|
|
|
|
ret =
|
|
(xmlSchematronParserCtxtPtr)
|
|
xmlMalloc(sizeof(xmlSchematronParserCtxt));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser context",
|
|
NULL);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronParserCtxt));
|
|
ret->type = XML_STRON_CTXT_PARSER;
|
|
ret->dict = xmlDictCreate();
|
|
ret->URL = xmlDictLookup(ret->dict, (const xmlChar *) URL, -1);
|
|
ret->includes = NULL;
|
|
ret->xctxt = xmlXPathNewContext(NULL);
|
|
if (ret->xctxt == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
|
|
NULL);
|
|
xmlSchematronFreeParserCtxt(ret);
|
|
return (NULL);
|
|
}
|
|
ret->xctxt->flags = XML_XPATH_CHECKNS;
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronNewMemParserCtxt:
|
|
* @buffer: a pointer to a char array containing the schemas
|
|
* @size: the size of the array
|
|
*
|
|
* Create an XML Schematrons parse context for that memory buffer expected
|
|
* to contain an XML Schematrons file.
|
|
*
|
|
* Returns the parser context or NULL in case of error
|
|
*/
|
|
xmlSchematronParserCtxtPtr
|
|
xmlSchematronNewMemParserCtxt(const char *buffer, int size)
|
|
{
|
|
xmlSchematronParserCtxtPtr ret;
|
|
|
|
if ((buffer == NULL) || (size <= 0))
|
|
return (NULL);
|
|
|
|
ret =
|
|
(xmlSchematronParserCtxtPtr)
|
|
xmlMalloc(sizeof(xmlSchematronParserCtxt));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser context",
|
|
NULL);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronParserCtxt));
|
|
ret->buffer = buffer;
|
|
ret->size = size;
|
|
ret->dict = xmlDictCreate();
|
|
ret->xctxt = xmlXPathNewContext(NULL);
|
|
if (ret->xctxt == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
|
|
NULL);
|
|
xmlSchematronFreeParserCtxt(ret);
|
|
return (NULL);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronNewDocParserCtxt:
|
|
* @doc: a preparsed document tree
|
|
*
|
|
* Create an XML Schematrons parse context for that document.
|
|
* NB. The document may be modified during the parsing process.
|
|
*
|
|
* Returns the parser context or NULL in case of error
|
|
*/
|
|
xmlSchematronParserCtxtPtr
|
|
xmlSchematronNewDocParserCtxt(xmlDocPtr doc)
|
|
{
|
|
xmlSchematronParserCtxtPtr ret;
|
|
|
|
if (doc == NULL)
|
|
return (NULL);
|
|
|
|
ret =
|
|
(xmlSchematronParserCtxtPtr)
|
|
xmlMalloc(sizeof(xmlSchematronParserCtxt));
|
|
if (ret == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser context",
|
|
NULL);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronParserCtxt));
|
|
ret->doc = doc;
|
|
ret->dict = xmlDictCreate();
|
|
/* The application has responsibility for the document */
|
|
ret->preserve = 1;
|
|
ret->xctxt = xmlXPathNewContext(doc);
|
|
if (ret->xctxt == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
|
|
NULL);
|
|
xmlSchematronFreeParserCtxt(ret);
|
|
return (NULL);
|
|
}
|
|
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFreeParserCtxt:
|
|
* @ctxt: the schema parser context
|
|
*
|
|
* Free the resources associated to the schema parser context
|
|
*/
|
|
void
|
|
xmlSchematronFreeParserCtxt(xmlSchematronParserCtxtPtr ctxt)
|
|
{
|
|
if (ctxt == NULL)
|
|
return;
|
|
if (ctxt->doc != NULL && !ctxt->preserve)
|
|
xmlFreeDoc(ctxt->doc);
|
|
if (ctxt->xctxt != NULL) {
|
|
xmlXPathFreeContext(ctxt->xctxt);
|
|
}
|
|
if (ctxt->namespaces != NULL)
|
|
xmlFree((char **) ctxt->namespaces);
|
|
xmlDictFree(ctxt->dict);
|
|
xmlFree(ctxt);
|
|
}
|
|
|
|
#if 0
|
|
/**
|
|
* xmlSchematronPushInclude:
|
|
* @ctxt: the schema parser context
|
|
* @doc: the included document
|
|
* @cur: the current include node
|
|
*
|
|
* Add an included document
|
|
*/
|
|
static void
|
|
xmlSchematronPushInclude(xmlSchematronParserCtxtPtr ctxt,
|
|
xmlDocPtr doc, xmlNodePtr cur)
|
|
{
|
|
if (ctxt->includes == NULL) {
|
|
ctxt->maxIncludes = 10;
|
|
ctxt->includes = (xmlNodePtr *)
|
|
xmlMalloc(ctxt->maxIncludes * 2 * sizeof(xmlNodePtr));
|
|
if (ctxt->includes == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating parser includes",
|
|
NULL);
|
|
return;
|
|
}
|
|
ctxt->nbIncludes = 0;
|
|
} else if (ctxt->nbIncludes + 2 >= ctxt->maxIncludes) {
|
|
xmlNodePtr *tmp;
|
|
|
|
tmp = (xmlNodePtr *)
|
|
xmlRealloc(ctxt->includes, ctxt->maxIncludes * 4 *
|
|
sizeof(xmlNodePtr));
|
|
if (tmp == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating parser includes",
|
|
NULL);
|
|
return;
|
|
}
|
|
ctxt->includes = tmp;
|
|
ctxt->maxIncludes *= 2;
|
|
}
|
|
ctxt->includes[2 * ctxt->nbIncludes] = cur;
|
|
ctxt->includes[2 * ctxt->nbIncludes + 1] = (xmlNodePtr) doc;
|
|
ctxt->nbIncludes++;
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronPopInclude:
|
|
* @ctxt: the schema parser context
|
|
*
|
|
* Pop an include level. The included document is being freed
|
|
*
|
|
* Returns the node immediately following the include or NULL if the
|
|
* include list was empty.
|
|
*/
|
|
static xmlNodePtr
|
|
xmlSchematronPopInclude(xmlSchematronParserCtxtPtr ctxt)
|
|
{
|
|
xmlDocPtr doc;
|
|
xmlNodePtr ret;
|
|
|
|
if (ctxt->nbIncludes <= 0)
|
|
return(NULL);
|
|
ctxt->nbIncludes--;
|
|
doc = (xmlDocPtr) ctxt->includes[2 * ctxt->nbIncludes + 1];
|
|
ret = ctxt->includes[2 * ctxt->nbIncludes];
|
|
xmlFreeDoc(doc);
|
|
if (ret != NULL)
|
|
ret = ret->next;
|
|
if (ret == NULL)
|
|
return(xmlSchematronPopInclude(ctxt));
|
|
return(ret);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* xmlSchematronAddNamespace:
|
|
* @ctxt: the schema parser context
|
|
* @prefix: the namespace prefix
|
|
* @ns: the namespace name
|
|
*
|
|
* Add a namespace definition in the context
|
|
*/
|
|
static void
|
|
xmlSchematronAddNamespace(xmlSchematronParserCtxtPtr ctxt,
|
|
const xmlChar *prefix, const xmlChar *ns)
|
|
{
|
|
if (ctxt->namespaces == NULL) {
|
|
ctxt->maxNamespaces = 10;
|
|
ctxt->namespaces = (const xmlChar **)
|
|
xmlMalloc(ctxt->maxNamespaces * 2 * sizeof(const xmlChar *));
|
|
if (ctxt->namespaces == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
|
|
NULL);
|
|
return;
|
|
}
|
|
ctxt->nbNamespaces = 0;
|
|
} else if (ctxt->nbNamespaces + 2 >= ctxt->maxNamespaces) {
|
|
const xmlChar **tmp;
|
|
|
|
tmp = (const xmlChar **)
|
|
xmlRealloc((xmlChar **) ctxt->namespaces, ctxt->maxNamespaces * 4 *
|
|
sizeof(const xmlChar *));
|
|
if (tmp == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
|
|
NULL);
|
|
return;
|
|
}
|
|
ctxt->namespaces = tmp;
|
|
ctxt->maxNamespaces *= 2;
|
|
}
|
|
ctxt->namespaces[2 * ctxt->nbNamespaces] =
|
|
xmlDictLookup(ctxt->dict, ns, -1);
|
|
ctxt->namespaces[2 * ctxt->nbNamespaces + 1] =
|
|
xmlDictLookup(ctxt->dict, prefix, -1);
|
|
ctxt->nbNamespaces++;
|
|
ctxt->namespaces[2 * ctxt->nbNamespaces] = NULL;
|
|
ctxt->namespaces[2 * ctxt->nbNamespaces + 1] = NULL;
|
|
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronParseRule:
|
|
* @ctxt: a schema validation context
|
|
* @rule: the rule node
|
|
*
|
|
* parse a rule element
|
|
*/
|
|
static void
|
|
xmlSchematronParseRule(xmlSchematronParserCtxtPtr ctxt,
|
|
xmlSchematronPatternPtr pattern,
|
|
xmlNodePtr rule)
|
|
{
|
|
xmlNodePtr cur;
|
|
int nbChecks = 0;
|
|
xmlChar *test;
|
|
xmlChar *context;
|
|
xmlChar *report;
|
|
xmlSchematronRulePtr ruleptr;
|
|
xmlSchematronTestPtr testptr;
|
|
|
|
if ((ctxt == NULL) || (rule == NULL)) return;
|
|
|
|
context = xmlGetNoNsProp(rule, BAD_CAST "context");
|
|
if (context == NULL) {
|
|
xmlSchematronPErr(ctxt, rule,
|
|
XML_SCHEMAP_NOROOT,
|
|
"rule has no context attribute",
|
|
NULL, NULL);
|
|
return;
|
|
} else if (context[0] == 0) {
|
|
xmlSchematronPErr(ctxt, rule,
|
|
XML_SCHEMAP_NOROOT,
|
|
"rule has an empty context attribute",
|
|
NULL, NULL);
|
|
xmlFree(context);
|
|
return;
|
|
} else {
|
|
ruleptr = xmlSchematronAddRule(ctxt, ctxt->schema, pattern,
|
|
rule, context, NULL);
|
|
if (ruleptr == NULL) {
|
|
xmlFree(context);
|
|
return;
|
|
}
|
|
}
|
|
|
|
cur = rule->children;
|
|
NEXT_SCHEMATRON(cur);
|
|
while (cur != NULL) {
|
|
if (IS_SCHEMATRON(cur, "assert")) {
|
|
nbChecks++;
|
|
test = xmlGetNoNsProp(cur, BAD_CAST "test");
|
|
if (test == NULL) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"assert has no test attribute",
|
|
NULL, NULL);
|
|
} else if (test[0] == 0) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"assert has an empty test attribute",
|
|
NULL, NULL);
|
|
xmlFree(test);
|
|
} else {
|
|
/* TODO will need dynamic processing instead */
|
|
report = xmlNodeGetContent(cur);
|
|
|
|
testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_ASSERT,
|
|
ruleptr, cur, test, report);
|
|
if (testptr == NULL)
|
|
xmlFree(test);
|
|
}
|
|
} else if (IS_SCHEMATRON(cur, "report")) {
|
|
nbChecks++;
|
|
test = xmlGetNoNsProp(cur, BAD_CAST "test");
|
|
if (test == NULL) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"assert has no test attribute",
|
|
NULL, NULL);
|
|
} else if (test[0] == 0) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"assert has an empty test attribute",
|
|
NULL, NULL);
|
|
xmlFree(test);
|
|
} else {
|
|
/* TODO will need dynamic processing instead */
|
|
report = xmlNodeGetContent(cur);
|
|
|
|
testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_REPORT,
|
|
ruleptr, cur, test, report);
|
|
if (testptr == NULL)
|
|
xmlFree(test);
|
|
}
|
|
} else {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Expecting an assert or a report element instead of %s",
|
|
cur->name, NULL);
|
|
}
|
|
cur = cur->next;
|
|
NEXT_SCHEMATRON(cur);
|
|
}
|
|
if (nbChecks == 0) {
|
|
xmlSchematronPErr(ctxt, rule,
|
|
XML_SCHEMAP_NOROOT,
|
|
"rule has no assert nor report element", NULL, NULL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronParsePattern:
|
|
* @ctxt: a schema validation context
|
|
* @pat: the pattern node
|
|
*
|
|
* parse a pattern element
|
|
*/
|
|
static void
|
|
xmlSchematronParsePattern(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr pat)
|
|
{
|
|
xmlNodePtr cur;
|
|
xmlSchematronPatternPtr pattern;
|
|
int nbRules = 0;
|
|
xmlChar *id;
|
|
|
|
if ((ctxt == NULL) || (pat == NULL)) return;
|
|
|
|
id = xmlGetNoNsProp(pat, BAD_CAST "id");
|
|
if (id == NULL) {
|
|
id = xmlGetNoNsProp(pat, BAD_CAST "name");
|
|
}
|
|
pattern = xmlSchematronAddPattern(ctxt, ctxt->schema, pat, id);
|
|
if (pattern == NULL) {
|
|
if (id != NULL)
|
|
xmlFree(id);
|
|
return;
|
|
}
|
|
cur = pat->children;
|
|
NEXT_SCHEMATRON(cur);
|
|
while (cur != NULL) {
|
|
if (IS_SCHEMATRON(cur, "rule")) {
|
|
xmlSchematronParseRule(ctxt, pattern, cur);
|
|
nbRules++;
|
|
} else {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Expecting a rule element instead of %s", cur->name, NULL);
|
|
}
|
|
cur = cur->next;
|
|
NEXT_SCHEMATRON(cur);
|
|
}
|
|
if (nbRules == 0) {
|
|
xmlSchematronPErr(ctxt, pat,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Pattern has no rule element", NULL, NULL);
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
/**
|
|
* xmlSchematronLoadInclude:
|
|
* @ctxt: a schema validation context
|
|
* @cur: the include element
|
|
*
|
|
* Load the include document, Push the current pointer
|
|
*
|
|
* Returns the updated node pointer
|
|
*/
|
|
static xmlNodePtr
|
|
xmlSchematronLoadInclude(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr cur)
|
|
{
|
|
xmlNodePtr ret = NULL;
|
|
xmlDocPtr doc = NULL;
|
|
xmlChar *href = NULL;
|
|
xmlChar *base = NULL;
|
|
xmlChar *URI = NULL;
|
|
|
|
if ((ctxt == NULL) || (cur == NULL))
|
|
return(NULL);
|
|
|
|
href = xmlGetNoNsProp(cur, BAD_CAST "href");
|
|
if (href == NULL) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Include has no href attribute", NULL, NULL);
|
|
return(cur->next);
|
|
}
|
|
|
|
/* do the URI base composition, load and find the root */
|
|
base = xmlNodeGetBase(cur->doc, cur);
|
|
URI = xmlBuildURI(href, base);
|
|
doc = xmlReadFile((const char *) URI, NULL, SCHEMATRON_PARSE_OPTIONS);
|
|
if (doc == NULL) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_FAILED_LOAD,
|
|
"could not load include '%s'.\n",
|
|
URI, NULL);
|
|
goto done;
|
|
}
|
|
ret = xmlDocGetRootElement(doc);
|
|
if (ret == NULL) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_FAILED_LOAD,
|
|
"could not find root from include '%s'.\n",
|
|
URI, NULL);
|
|
goto done;
|
|
}
|
|
|
|
/* Success, push the include for rollback on exit */
|
|
xmlSchematronPushInclude(ctxt, doc, cur);
|
|
|
|
done:
|
|
if (ret == NULL) {
|
|
if (doc != NULL)
|
|
xmlFreeDoc(doc);
|
|
}
|
|
xmlFree(href);
|
|
if (base != NULL)
|
|
xmlFree(base);
|
|
if (URI != NULL)
|
|
xmlFree(URI);
|
|
return(ret);
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* xmlSchematronParse:
|
|
* @ctxt: a schema validation context
|
|
*
|
|
* parse a schema definition resource and build an internal
|
|
* XML Shema struture which can be used to validate instances.
|
|
*
|
|
* Returns the internal XML Schematron structure built from the resource or
|
|
* NULL in case of error
|
|
*/
|
|
xmlSchematronPtr
|
|
xmlSchematronParse(xmlSchematronParserCtxtPtr ctxt)
|
|
{
|
|
xmlSchematronPtr ret = NULL;
|
|
xmlDocPtr doc;
|
|
xmlNodePtr root, cur;
|
|
int preserve = 0;
|
|
|
|
if (ctxt == NULL)
|
|
return (NULL);
|
|
|
|
ctxt->nberrors = 0;
|
|
|
|
/*
|
|
* First step is to parse the input document into an DOM/Infoset
|
|
*/
|
|
if (ctxt->URL != NULL) {
|
|
doc = xmlReadFile((const char *) ctxt->URL, NULL,
|
|
SCHEMATRON_PARSE_OPTIONS);
|
|
if (doc == NULL) {
|
|
xmlSchematronPErr(ctxt, NULL,
|
|
XML_SCHEMAP_FAILED_LOAD,
|
|
"xmlSchematronParse: could not load '%s'.\n",
|
|
ctxt->URL, NULL);
|
|
return (NULL);
|
|
}
|
|
ctxt->preserve = 0;
|
|
} else if (ctxt->buffer != NULL) {
|
|
doc = xmlReadMemory(ctxt->buffer, ctxt->size, NULL, NULL,
|
|
SCHEMATRON_PARSE_OPTIONS);
|
|
if (doc == NULL) {
|
|
xmlSchematronPErr(ctxt, NULL,
|
|
XML_SCHEMAP_FAILED_PARSE,
|
|
"xmlSchematronParse: could not parse.\n",
|
|
NULL, NULL);
|
|
return (NULL);
|
|
}
|
|
doc->URL = xmlStrdup(BAD_CAST "in_memory_buffer");
|
|
ctxt->URL = xmlDictLookup(ctxt->dict, BAD_CAST "in_memory_buffer", -1);
|
|
ctxt->preserve = 0;
|
|
} else if (ctxt->doc != NULL) {
|
|
doc = ctxt->doc;
|
|
preserve = 1;
|
|
ctxt->preserve = 1;
|
|
} else {
|
|
xmlSchematronPErr(ctxt, NULL,
|
|
XML_SCHEMAP_NOTHING_TO_PARSE,
|
|
"xmlSchematronParse: could not parse.\n",
|
|
NULL, NULL);
|
|
return (NULL);
|
|
}
|
|
|
|
/*
|
|
* Then extract the root and Schematron parse it
|
|
*/
|
|
root = xmlDocGetRootElement(doc);
|
|
if (root == NULL) {
|
|
xmlSchematronPErr(ctxt, (xmlNodePtr) doc,
|
|
XML_SCHEMAP_NOROOT,
|
|
"The schema has no document element.\n", NULL, NULL);
|
|
if (!preserve) {
|
|
xmlFreeDoc(doc);
|
|
}
|
|
return (NULL);
|
|
}
|
|
|
|
if (!IS_SCHEMATRON(root, "schema")) {
|
|
xmlSchematronPErr(ctxt, root,
|
|
XML_SCHEMAP_NOROOT,
|
|
"The XML document '%s' is not a XML schematron document",
|
|
ctxt->URL, NULL);
|
|
goto exit;
|
|
}
|
|
ret = xmlSchematronNewSchematron(ctxt);
|
|
if (ret == NULL)
|
|
goto exit;
|
|
ctxt->schema = ret;
|
|
|
|
/*
|
|
* scan the schema elements
|
|
*/
|
|
cur = root->children;
|
|
NEXT_SCHEMATRON(cur);
|
|
if (IS_SCHEMATRON(cur, "title")) {
|
|
xmlChar *title = xmlNodeGetContent(cur);
|
|
if (title != NULL) {
|
|
ret->title = xmlDictLookup(ret->dict, title, -1);
|
|
xmlFree(title);
|
|
}
|
|
cur = cur->next;
|
|
NEXT_SCHEMATRON(cur);
|
|
}
|
|
while (IS_SCHEMATRON(cur, "ns")) {
|
|
xmlChar *prefix = xmlGetNoNsProp(cur, BAD_CAST "prefix");
|
|
xmlChar *uri = xmlGetNoNsProp(cur, BAD_CAST "uri");
|
|
if ((uri == NULL) || (uri[0] == 0)) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"ns element has no uri", NULL, NULL);
|
|
}
|
|
if ((prefix == NULL) || (prefix[0] == 0)) {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"ns element has no prefix", NULL, NULL);
|
|
}
|
|
if ((prefix) && (uri)) {
|
|
xmlXPathRegisterNs(ctxt->xctxt, prefix, uri);
|
|
xmlSchematronAddNamespace(ctxt, prefix, uri);
|
|
ret->nbNs++;
|
|
}
|
|
if (uri)
|
|
xmlFree(uri);
|
|
if (prefix)
|
|
xmlFree(prefix);
|
|
cur = cur->next;
|
|
NEXT_SCHEMATRON(cur);
|
|
}
|
|
while (cur != NULL) {
|
|
if (IS_SCHEMATRON(cur, "pattern")) {
|
|
xmlSchematronParsePattern(ctxt, cur);
|
|
ret->nbPattern++;
|
|
} else {
|
|
xmlSchematronPErr(ctxt, cur,
|
|
XML_SCHEMAP_NOROOT,
|
|
"Expecting a pattern element instead of %s", cur->name, NULL);
|
|
}
|
|
cur = cur->next;
|
|
NEXT_SCHEMATRON(cur);
|
|
}
|
|
if (ret->nbPattern == 0) {
|
|
xmlSchematronPErr(ctxt, root,
|
|
XML_SCHEMAP_NOROOT,
|
|
"The schematron document '%s' has no pattern",
|
|
ctxt->URL, NULL);
|
|
goto exit;
|
|
}
|
|
/* the original document must be kept for reporting */
|
|
ret->doc = doc;
|
|
if (preserve) {
|
|
ret->preserve = 1;
|
|
}
|
|
preserve = 1;
|
|
|
|
exit:
|
|
if (!preserve) {
|
|
xmlFreeDoc(doc);
|
|
}
|
|
if (ret != NULL) {
|
|
if (ctxt->nberrors != 0) {
|
|
xmlSchematronFree(ret);
|
|
ret = NULL;
|
|
} else {
|
|
ret->namespaces = ctxt->namespaces;
|
|
ret->nbNamespaces = ctxt->nbNamespaces;
|
|
ctxt->namespaces = NULL;
|
|
}
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/************************************************************************
|
|
* *
|
|
* Schematrontron Reports handler *
|
|
* *
|
|
************************************************************************/
|
|
|
|
static xmlNodePtr
|
|
xmlSchematronGetNode(xmlSchematronValidCtxtPtr ctxt,
|
|
xmlNodePtr cur, const xmlChar *xpath) {
|
|
xmlNodePtr node = NULL;
|
|
xmlXPathObjectPtr ret;
|
|
|
|
if ((ctxt == NULL) || (cur == NULL) || (xpath == NULL))
|
|
return(NULL);
|
|
|
|
ctxt->xctxt->doc = cur->doc;
|
|
ctxt->xctxt->node = cur;
|
|
ret = xmlXPathEval(xpath, ctxt->xctxt);
|
|
if (ret == NULL)
|
|
return(NULL);
|
|
|
|
if ((ret->type == XPATH_NODESET) &&
|
|
(ret->nodesetval != NULL) && (ret->nodesetval->nodeNr > 0))
|
|
node = ret->nodesetval->nodeTab[0];
|
|
|
|
xmlXPathFreeObject(ret);
|
|
return(node);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronReportOutput:
|
|
* @ctxt: the validation context
|
|
* @cur: the current node tested
|
|
* @msg: the message output
|
|
*
|
|
* Output part of the report to whatever channel the user selected
|
|
*/
|
|
static void
|
|
xmlSchematronReportOutput(xmlSchematronValidCtxtPtr ctxt ATTRIBUTE_UNUSED,
|
|
xmlNodePtr cur ATTRIBUTE_UNUSED,
|
|
const char *msg) {
|
|
/* TODO */
|
|
fprintf(stderr, "%s", msg);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFormatReport:
|
|
* @ctxt: the validation context
|
|
* @test: the test node
|
|
* @cur: the current node tested
|
|
*
|
|
* Build the string being reported to the user.
|
|
*
|
|
* Returns a report string or NULL in case of error. The string needs
|
|
* to be deallocated by teh caller
|
|
*/
|
|
static xmlChar *
|
|
xmlSchematronFormatReport(xmlSchematronValidCtxtPtr ctxt,
|
|
xmlNodePtr test, xmlNodePtr cur) {
|
|
xmlChar *ret = NULL;
|
|
xmlNodePtr child, node;
|
|
|
|
if ((test == NULL) || (cur == NULL))
|
|
return(ret);
|
|
|
|
child = test->children;
|
|
while (child != NULL) {
|
|
if ((child->type == XML_TEXT_NODE) ||
|
|
(child->type == XML_CDATA_SECTION_NODE))
|
|
ret = xmlStrcat(ret, child->content);
|
|
else if (IS_SCHEMATRON(child, "name")) {
|
|
xmlChar *path;
|
|
|
|
path = xmlGetNoNsProp(child, BAD_CAST "path");
|
|
|
|
node = cur;
|
|
if (path != NULL) {
|
|
node = xmlSchematronGetNode(ctxt, cur, path);
|
|
if (node == NULL)
|
|
node = cur;
|
|
xmlFree(path);
|
|
}
|
|
|
|
if ((node->ns == NULL) || (node->ns->prefix == NULL))
|
|
ret = xmlStrcat(ret, node->name);
|
|
else {
|
|
ret = xmlStrcat(ret, node->ns->prefix);
|
|
ret = xmlStrcat(ret, BAD_CAST ":");
|
|
ret = xmlStrcat(ret, node->name);
|
|
}
|
|
} else {
|
|
child = child->next;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* remove superfluous \n
|
|
*/
|
|
if (ret != NULL) {
|
|
int len = xmlStrlen(ret);
|
|
xmlChar c;
|
|
|
|
if (len > 0) {
|
|
c = ret[len - 1];
|
|
if ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
|
|
while ((c == ' ') || (c == '\n') ||
|
|
(c == '\r') || (c == '\t')) {
|
|
len--;
|
|
if (len == 0)
|
|
break;
|
|
c = ret[len - 1];
|
|
}
|
|
ret[len] = ' ';
|
|
ret[len + 1] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
child = child->next;
|
|
}
|
|
return(ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronReportSuccess:
|
|
* @ctxt: the validation context
|
|
* @test: the compiled test
|
|
* @cur: the current node tested
|
|
* @success: boolean value for the result
|
|
*
|
|
* called from the validation engine when an assert or report test have
|
|
* been done.
|
|
*/
|
|
static void
|
|
xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt,
|
|
xmlSchematronTestPtr test, xmlNodePtr cur, xmlSchematronPatternPtr pattern, int success) {
|
|
if ((ctxt == NULL) || (cur == NULL) || (test == NULL))
|
|
return;
|
|
/* if quiet and not SVRL report only failures */
|
|
if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) &&
|
|
((ctxt->flags & XML_SCHEMATRON_OUT_XML) == 0) &&
|
|
(test->type == XML_SCHEMATRON_REPORT))
|
|
return;
|
|
if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
|
|
TODO
|
|
} else {
|
|
xmlChar *path;
|
|
char msg[1000];
|
|
long line;
|
|
const xmlChar *report = NULL;
|
|
|
|
if (((test->type == XML_SCHEMATRON_REPORT) & (!success)) ||
|
|
((test->type == XML_SCHEMATRON_ASSERT) & (success)))
|
|
return;
|
|
line = xmlGetLineNo(cur);
|
|
path = xmlGetNodePath(cur);
|
|
if (path == NULL)
|
|
path = (xmlChar *) cur->name;
|
|
#if 0
|
|
if ((test->report != NULL) && (test->report[0] != 0))
|
|
report = test->report;
|
|
#endif
|
|
if (test->node != NULL)
|
|
report = xmlSchematronFormatReport(ctxt, test->node, cur);
|
|
if (report == NULL) {
|
|
if (test->type == XML_SCHEMATRON_ASSERT) {
|
|
report = xmlStrdup((const xmlChar *) "node failed assert");
|
|
} else {
|
|
report = xmlStrdup((const xmlChar *) "node failed report");
|
|
}
|
|
}
|
|
snprintf(msg, 999, "%s line %ld: %s\n", (const char *) path,
|
|
line, (const char *) report);
|
|
|
|
if (ctxt->flags & XML_SCHEMATRON_OUT_ERROR) {
|
|
xmlStructuredErrorFunc schannel = NULL;
|
|
xmlGenericErrorFunc channel = NULL;
|
|
void *data = NULL;
|
|
|
|
if (ctxt != NULL) {
|
|
if (ctxt->serror != NULL)
|
|
schannel = ctxt->serror;
|
|
else
|
|
channel = ctxt->error;
|
|
data = ctxt->userData;
|
|
}
|
|
|
|
__xmlRaiseError(schannel, channel, data,
|
|
NULL, cur, XML_FROM_SCHEMATRONV,
|
|
(test->type == XML_SCHEMATRON_ASSERT)?XML_SCHEMATRONV_ASSERT:XML_SCHEMATRONV_REPORT,
|
|
XML_ERR_ERROR, NULL, line,
|
|
(pattern == NULL)?NULL:((const char *) pattern->name),
|
|
(const char *) path,
|
|
(const char *) report, 0, 0,
|
|
"%s", msg);
|
|
} else {
|
|
xmlSchematronReportOutput(ctxt, cur, &msg[0]);
|
|
}
|
|
|
|
xmlFree((char *) report);
|
|
|
|
if ((path != NULL) && (path != (xmlChar *) cur->name))
|
|
xmlFree(path);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronReportPattern:
|
|
* @ctxt: the validation context
|
|
* @pattern: the current pattern
|
|
*
|
|
* called from the validation engine when starting to check a pattern
|
|
*/
|
|
static void
|
|
xmlSchematronReportPattern(xmlSchematronValidCtxtPtr ctxt,
|
|
xmlSchematronPatternPtr pattern) {
|
|
if ((ctxt == NULL) || (pattern == NULL))
|
|
return;
|
|
if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) || (ctxt->flags & XML_SCHEMATRON_OUT_ERROR)) /* Error gives pattern name as part of error */
|
|
return;
|
|
if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
|
|
TODO
|
|
} else {
|
|
char msg[1000];
|
|
|
|
if (pattern->name == NULL)
|
|
return;
|
|
snprintf(msg, 999, "Pattern: %s\n", (const char *) pattern->name);
|
|
xmlSchematronReportOutput(ctxt, NULL, &msg[0]);
|
|
}
|
|
}
|
|
|
|
|
|
/************************************************************************
|
|
* *
|
|
* Validation against a Schematrontron *
|
|
* *
|
|
************************************************************************/
|
|
|
|
/**
|
|
* xmlSchematronSetValidStructuredErrors:
|
|
* @ctxt: a Schematron validation context
|
|
* @serror: the structured error function
|
|
* @ctx: the functions context
|
|
*
|
|
* Set the structured error callback
|
|
*/
|
|
void
|
|
xmlSchematronSetValidStructuredErrors(xmlSchematronValidCtxtPtr ctxt,
|
|
xmlStructuredErrorFunc serror, void *ctx)
|
|
{
|
|
if (ctxt == NULL)
|
|
return;
|
|
ctxt->serror = serror;
|
|
ctxt->error = NULL;
|
|
ctxt->warning = NULL;
|
|
ctxt->userData = ctx;
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronNewValidCtxt:
|
|
* @schema: a precompiled XML Schematrons
|
|
* @options: a set of xmlSchematronValidOptions
|
|
*
|
|
* Create an XML Schematrons validation context based on the given schema.
|
|
*
|
|
* Returns the validation context or NULL in case of error
|
|
*/
|
|
xmlSchematronValidCtxtPtr
|
|
xmlSchematronNewValidCtxt(xmlSchematronPtr schema, int options)
|
|
{
|
|
int i;
|
|
xmlSchematronValidCtxtPtr ret;
|
|
|
|
ret = (xmlSchematronValidCtxtPtr) xmlMalloc(sizeof(xmlSchematronValidCtxt));
|
|
if (ret == NULL) {
|
|
xmlSchematronVErrMemory(NULL, "allocating validation context",
|
|
NULL);
|
|
return (NULL);
|
|
}
|
|
memset(ret, 0, sizeof(xmlSchematronValidCtxt));
|
|
ret->type = XML_STRON_CTXT_VALIDATOR;
|
|
ret->schema = schema;
|
|
ret->xctxt = xmlXPathNewContext(NULL);
|
|
ret->flags = options;
|
|
if (ret->xctxt == NULL) {
|
|
xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
|
|
NULL);
|
|
xmlSchematronFreeValidCtxt(ret);
|
|
return (NULL);
|
|
}
|
|
for (i = 0;i < schema->nbNamespaces;i++) {
|
|
if ((schema->namespaces[2 * i] == NULL) ||
|
|
(schema->namespaces[2 * i + 1] == NULL))
|
|
break;
|
|
xmlXPathRegisterNs(ret->xctxt, schema->namespaces[2 * i + 1],
|
|
schema->namespaces[2 * i]);
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronFreeValidCtxt:
|
|
* @ctxt: the schema validation context
|
|
*
|
|
* Free the resources associated to the schema validation context
|
|
*/
|
|
void
|
|
xmlSchematronFreeValidCtxt(xmlSchematronValidCtxtPtr ctxt)
|
|
{
|
|
if (ctxt == NULL)
|
|
return;
|
|
if (ctxt->xctxt != NULL)
|
|
xmlXPathFreeContext(ctxt->xctxt);
|
|
if (ctxt->dict != NULL)
|
|
xmlDictFree(ctxt->dict);
|
|
xmlFree(ctxt);
|
|
}
|
|
|
|
static xmlNodePtr
|
|
xmlSchematronNextNode(xmlNodePtr cur) {
|
|
if (cur->children != NULL) {
|
|
/*
|
|
* Do not descend on entities declarations
|
|
*/
|
|
if (cur->children->type != XML_ENTITY_DECL) {
|
|
cur = cur->children;
|
|
/*
|
|
* Skip DTDs
|
|
*/
|
|
if (cur->type != XML_DTD_NODE)
|
|
return(cur);
|
|
}
|
|
}
|
|
|
|
while (cur->next != NULL) {
|
|
cur = cur->next;
|
|
if ((cur->type != XML_ENTITY_DECL) &&
|
|
(cur->type != XML_DTD_NODE))
|
|
return(cur);
|
|
}
|
|
|
|
do {
|
|
cur = cur->parent;
|
|
if (cur == NULL) break;
|
|
if (cur->type == XML_DOCUMENT_NODE) return(NULL);
|
|
if (cur->next != NULL) {
|
|
cur = cur->next;
|
|
return(cur);
|
|
}
|
|
} while (cur != NULL);
|
|
return(cur);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronRunTest:
|
|
* @ctxt: the schema validation context
|
|
* @test: the current test
|
|
* @instance: the document instace tree
|
|
* @cur: the current node in the instance
|
|
*
|
|
* Validate a rule against a tree instance at a given position
|
|
*
|
|
* Returns 1 in case of success, 0 if error and -1 in case of internal error
|
|
*/
|
|
static int
|
|
xmlSchematronRunTest(xmlSchematronValidCtxtPtr ctxt,
|
|
xmlSchematronTestPtr test, xmlDocPtr instance, xmlNodePtr cur, xmlSchematronPatternPtr pattern)
|
|
{
|
|
xmlXPathObjectPtr ret;
|
|
int failed;
|
|
|
|
failed = 0;
|
|
ctxt->xctxt->doc = instance;
|
|
ctxt->xctxt->node = cur;
|
|
ret = xmlXPathCompiledEval(test->comp, ctxt->xctxt);
|
|
if (ret == NULL) {
|
|
failed = 1;
|
|
} else {
|
|
switch (ret->type) {
|
|
case XPATH_XSLT_TREE:
|
|
case XPATH_NODESET:
|
|
if ((ret->nodesetval == NULL) ||
|
|
(ret->nodesetval->nodeNr == 0))
|
|
failed = 1;
|
|
break;
|
|
case XPATH_BOOLEAN:
|
|
failed = !ret->boolval;
|
|
break;
|
|
case XPATH_NUMBER:
|
|
if ((xmlXPathIsNaN(ret->floatval)) ||
|
|
(ret->floatval == 0.0))
|
|
failed = 1;
|
|
break;
|
|
case XPATH_STRING:
|
|
if ((ret->stringval == NULL) ||
|
|
(ret->stringval[0] == 0))
|
|
failed = 1;
|
|
break;
|
|
case XPATH_UNDEFINED:
|
|
case XPATH_POINT:
|
|
case XPATH_RANGE:
|
|
case XPATH_LOCATIONSET:
|
|
case XPATH_USERS:
|
|
failed = 1;
|
|
break;
|
|
}
|
|
xmlXPathFreeObject(ret);
|
|
}
|
|
if ((failed) && (test->type == XML_SCHEMATRON_ASSERT))
|
|
ctxt->nberrors++;
|
|
else if ((!failed) && (test->type == XML_SCHEMATRON_REPORT))
|
|
ctxt->nberrors++;
|
|
|
|
xmlSchematronReportSuccess(ctxt, test, cur, pattern, !failed);
|
|
|
|
return(!failed);
|
|
}
|
|
|
|
/**
|
|
* xmlSchematronValidateDoc:
|
|
* @ctxt: the schema validation context
|
|
* @instance: the document instace tree
|
|
*
|
|
* Validate a tree instance against the schematron
|
|
*
|
|
* Returns 0 in case of success, -1 in case of internal error
|
|
* and an error count otherwise.
|
|
*/
|
|
int
|
|
xmlSchematronValidateDoc(xmlSchematronValidCtxtPtr ctxt, xmlDocPtr instance)
|
|
{
|
|
xmlNodePtr cur, root;
|
|
xmlSchematronPatternPtr pattern;
|
|
xmlSchematronRulePtr rule;
|
|
xmlSchematronTestPtr test;
|
|
|
|
if ((ctxt == NULL) || (ctxt->schema == NULL) ||
|
|
(ctxt->schema->rules == NULL) || (instance == NULL))
|
|
return(-1);
|
|
ctxt->nberrors = 0;
|
|
root = xmlDocGetRootElement(instance);
|
|
if (root == NULL) {
|
|
TODO
|
|
ctxt->nberrors++;
|
|
return(1);
|
|
}
|
|
if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) ||
|
|
(ctxt->flags == 0)) {
|
|
/*
|
|
* we are just trying to assert the validity of the document,
|
|
* speed primes over the output, run in a single pass
|
|
*/
|
|
cur = root;
|
|
while (cur != NULL) {
|
|
rule = ctxt->schema->rules;
|
|
while (rule != NULL) {
|
|
if (xmlPatternMatch(rule->pattern, cur) == 1) {
|
|
test = rule->tests;
|
|
while (test != NULL) {
|
|
xmlSchematronRunTest(ctxt, test, instance, cur, (xmlSchematronPatternPtr)rule->pattern);
|
|
test = test->next;
|
|
}
|
|
}
|
|
rule = rule->next;
|
|
}
|
|
|
|
cur = xmlSchematronNextNode(cur);
|
|
}
|
|
} else {
|
|
/*
|
|
* Process all contexts one at a time
|
|
*/
|
|
pattern = ctxt->schema->patterns;
|
|
|
|
while (pattern != NULL) {
|
|
xmlSchematronReportPattern(ctxt, pattern);
|
|
|
|
/*
|
|
* TODO convert the pattern rule to a direct XPath and
|
|
* compute directly instead of using the pattern matching
|
|
* over the full document...
|
|
* Check the exact semantic
|
|
*/
|
|
cur = root;
|
|
while (cur != NULL) {
|
|
rule = pattern->rules;
|
|
while (rule != NULL) {
|
|
if (xmlPatternMatch(rule->pattern, cur) == 1) {
|
|
test = rule->tests;
|
|
while (test != NULL) {
|
|
xmlSchematronRunTest(ctxt, test, instance, cur, pattern);
|
|
test = test->next;
|
|
}
|
|
}
|
|
rule = rule->patnext;
|
|
}
|
|
|
|
cur = xmlSchematronNextNode(cur);
|
|
}
|
|
pattern = pattern->next;
|
|
}
|
|
}
|
|
return(ctxt->nberrors);
|
|
}
|
|
|
|
#ifdef STANDALONE
|
|
int
|
|
main(void)
|
|
{
|
|
int ret;
|
|
xmlDocPtr instance;
|
|
xmlSchematronParserCtxtPtr pctxt;
|
|
xmlSchematronValidCtxtPtr vctxt;
|
|
xmlSchematronPtr schema = NULL;
|
|
|
|
pctxt = xmlSchematronNewParserCtxt("tst.sct");
|
|
if (pctxt == NULL) {
|
|
fprintf(stderr, "failed to build schematron parser\n");
|
|
} else {
|
|
schema = xmlSchematronParse(pctxt);
|
|
if (schema == NULL) {
|
|
fprintf(stderr, "failed to compile schematron\n");
|
|
}
|
|
xmlSchematronFreeParserCtxt(pctxt);
|
|
}
|
|
instance = xmlReadFile("tst.sct", NULL,
|
|
XML_PARSE_NOENT | XML_PARSE_NOCDATA);
|
|
if (instance == NULL) {
|
|
fprintf(stderr, "failed to parse instance\n");
|
|
}
|
|
if ((schema != NULL) && (instance != NULL)) {
|
|
vctxt = xmlSchematronNewValidCtxt(schema);
|
|
if (vctxt == NULL) {
|
|
fprintf(stderr, "failed to build schematron validator\n");
|
|
} else {
|
|
ret = xmlSchematronValidateDoc(vctxt, instance);
|
|
xmlSchematronFreeValidCtxt(vctxt);
|
|
}
|
|
}
|
|
xmlSchematronFree(schema);
|
|
xmlFreeDoc(instance);
|
|
|
|
xmlCleanupParser();
|
|
xmlMemoryDump();
|
|
|
|
return (0);
|
|
}
|
|
#endif
|
|
#define bottom_schematron
|
|
#include "elfgcchack.h"
|
|
#endif /* LIBXML_SCHEMATRON_ENABLED */
|