| /* | |
| * 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 */ |