| /* |
| * schemas.c : implementation of the Schematron schema validity checking |
| * |
| * See Copyright for the status of this software. |
| * |
| * Daniel Veillard <veillard@redhat.com> |
| */ |
| |
| #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 |
| |
| static const xmlChar *xmlSchematronNs = XML_SCHEMATRON_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))) |
| |
| #define NEXT_SCHEMATRON(node) \ |
| while (node != NULL) { \ |
| if ((node->type == XML_ELEMENT_NODE ) && (node->ns != NULL) && \ |
| (xmlStrEqual(node->ns->href, xmlSchematronNs))) \ |
| break; \ |
| node = node->next; \ |
| } |
| |
| /** |
| * TODO: |
| * |
| * macro to flag unimplemented blocks |
| */ |
| #define TODO \ |
| xmlGenericError(xmlGenericErrorContext, \ |
| "Unimplemented block at %s:%d\n", \ |
| __FILE__, __LINE__); |
| |
| #define XML_SCHEMATRON_ASSERT 0 |
| #define XML_SCHEMATRON_REPORT 1 |
| /** |
| * _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 */ |
| int type; /* 0 for assert, 1 for report */ |
| xmlNodePtr node; /* the node in the tree */ |
| xmlChar *test; /* the expression to test */ |
| xmlXPathCompExprPtr comp; /* the compiled expression */ |
| }; |
| |
| /** |
| * _xmlSchematronRule: |
| * |
| * A Schematrons rule |
| */ |
| typedef struct _xmlSchematronRule xmlSchematronRule; |
| typedef xmlSchematronRule *xmlSchematronRulePtr; |
| struct _xmlSchematronRule { |
| xmlSchematronRulePtr next; /* the next rule in the 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 */ |
| }; |
| |
| /** |
| * _xmlSchematron: |
| * |
| * A Schematrons definition |
| */ |
| struct _xmlSchematron { |
| const xmlChar *name; /* schema name */ |
| int preserve; /* was the document preserved 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 dictionnary used internally */ |
| |
| const xmlChar *title; /* the title if any */ |
| |
| int nbNs; /* the number of namespaces */ |
| |
| int nbPattern; /* the number of patterns */ |
| 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 */ |
| xmlOutputWriteCallback iowrite; /* if using XML_SCHEMATRON_OUT_IO */ |
| xmlOutputCloseCallback ioclose; |
| void *ioctx; |
| }; |
| |
| struct _xmlSchematronParserCtxt { |
| int type; |
| const xmlChar *URL; |
| xmlDocPtr doc; |
| int preserve; /* Whether the doc should be freed */ |
| const char *buffer; |
| int size; |
| |
| xmlDictPtr dict; /* dictionnary 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 rreporting 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 |
| 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 |
| * @schema: a schema structure |
| * @node: the node hosting the test |
| * @context: the associated context string |
| * |
| * Add a test to a schematron |
| * |
| * Returns the new pointer or NULL in case of error |
| */ |
| static xmlSchematronTestPtr |
| xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt, int type, |
| xmlSchematronRulePtr rule, |
| xmlNodePtr node, xmlChar *test) |
| { |
| 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->next = rule->tests; |
| rule->tests = 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); |
| 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 |
| * |
| * Add a rule to a schematron |
| * |
| * Returns the new pointer or NULL in case of error |
| */ |
| static xmlSchematronRulePtr |
| xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt, xmlSchematronPtr schema, |
| xmlNodePtr node, xmlChar *context) |
| { |
| 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->next = schema->rules; |
| ret->pattern = pattern; |
| schema->rules = 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); |
| xmlFree(rules); |
| rules = 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(schema->namespaces); |
| |
| xmlSchematronFreeRules(schema->rules); |
| 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 = 0; |
| 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(ctxt->namespaces); |
| xmlDictFree(ctxt->dict); |
| xmlFree(ctxt); |
| } |
| |
| /** |
| * 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); |
| } |
| |
| /** |
| * 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(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, xmlNodePtr rule) |
| { |
| xmlNodePtr cur; |
| int nbChecks = 0; |
| xmlChar *test; |
| xmlChar *context; |
| 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, rule, context); |
| 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 { |
| testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_ASSERT, |
| ruleptr, cur, test); |
| 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 { |
| testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_REPORT, |
| ruleptr, cur, test); |
| 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; |
| int nbRules = 0; |
| |
| if ((ctxt == NULL) || (pat == NULL)) return; |
| |
| cur = pat->children; |
| NEXT_SCHEMATRON(cur); |
| while (cur != NULL) { |
| if (IS_SCHEMATRON(cur, "rule")) { |
| xmlSchematronParseRule(ctxt, 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); |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| if (href == NULL) |
| xmlFree(href); |
| if (base == NULL) |
| xmlFree(base); |
| if (URI == NULL) |
| xmlFree(URI); |
| return(ret); |
| } |
| |
| /** |
| * 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); |
| } |
| } 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); |
| } else if (ctxt->doc != NULL) { |
| doc = ctxt->doc; |
| 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; |
| } |
| |
| exit: |
| if (!preserve) { |
| xmlFreeDoc(doc); |
| } |
| 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 void |
| xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt, |
| xmlSchematronTestPtr test, xmlNodePtr cur) { |
| } |
| |
| /************************************************************************ |
| * * |
| * Validation against a Schematrontron * |
| * * |
| ************************************************************************/ |
| |
| /** |
| * 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); |
| 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) return(NULL); |
| 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) |
| { |
| 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; |
| } |
| if (test->type == XML_SCHEMATRON_REPORT) { |
| if (!failed) { |
| printf("report failed\n"); |
| } |
| } else { |
| if (failed) { |
| printf("assert failed\n"); |
| } |
| } |
| |
| 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; |
| xmlSchematronRulePtr rule; |
| xmlSchematronTestPtr test; |
| |
| if ((ctxt == NULL) || (ctxt->schema == NULL) || |
| (ctxt->schema->rules == NULL) || (instance == NULL)) |
| return(-1); |
| ctxt->nberrors = 0; |
| cur = xmlDocGetRootElement(instance); |
| while (cur != NULL) { |
| rule = ctxt->schema->rules; |
| while (rule != NULL) { |
| if (xmlPatternMatch(rule->pattern, cur) == 1) { |
| printf("%s matches\n", cur->name); |
| test = rule->tests; |
| while (test != NULL) { |
| xmlSchematronRunTest(ctxt, test, instance, cur); |
| test = test->next; |
| } |
| } |
| rule = rule->next; |
| } |
| |
| cur = xmlSchematronNextNode(cur); |
| } |
| 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 |
| #endif /* LIBXML_SCHEMATRON_ENABLED */ |