blob: 4211b78f62e7431247d92338960b80906d4c831e [file] [log] [blame]
Daniel Veillarded6c5492005-07-23 15:00:22 +00001/*
2 * schemas.c : implementation of the Schematron schema validity checking
3 *
4 * See Copyright for the status of this software.
5 *
6 * Daniel Veillard <veillard@redhat.com>
7 */
8
9#define IN_LIBXML
10#include "libxml.h"
11
12#ifdef LIBXML_SCHEMATRON_ENABLED
13
14#include <string.h>
15#include <libxml/parser.h>
16#include <libxml/tree.h>
17#include <libxml/uri.h>
18#include <libxml/xpath.h>
19#include <libxml/xpathInternals.h>
20#include <libxml/pattern.h>
21#include <libxml/schematron.h>
22
23#define SCHEMATRON_PARSE_OPTIONS XML_PARSE_NOENT
24
25static const xmlChar *xmlSchematronNs = XML_SCHEMATRON_NS;
26
27#define IS_SCHEMATRON(node, elem) \
28 ((node != NULL) && (node->type == XML_ELEMENT_NODE ) && \
29 (node->ns != NULL) && \
30 (xmlStrEqual(node->name, (const xmlChar *) elem)) && \
31 (xmlStrEqual(node->ns->href, xmlSchematronNs)))
32
33#define NEXT_SCHEMATRON(node) \
34 while (node != NULL) { \
35 if ((node->type == XML_ELEMENT_NODE ) && (node->ns != NULL) && \
36 (xmlStrEqual(node->ns->href, xmlSchematronNs))) \
37 break; \
38 node = node->next; \
39 }
40
41/**
42 * TODO:
43 *
44 * macro to flag unimplemented blocks
45 */
46#define TODO \
47 xmlGenericError(xmlGenericErrorContext, \
48 "Unimplemented block at %s:%d\n", \
49 __FILE__, __LINE__);
50
51#define XML_SCHEMATRON_ASSERT 0
52#define XML_SCHEMATRON_REPORT 1
53/**
54 * _xmlSchematronTest:
55 *
56 * A Schematrons test, either an assert or a report
57 */
58typedef struct _xmlSchematronTest xmlSchematronTest;
59typedef xmlSchematronTest *xmlSchematronTestPtr;
60struct _xmlSchematronTest {
61 xmlSchematronTestPtr next; /* the next test in the list */
62 int type; /* 0 for assert, 1 for report */
63 xmlNodePtr node; /* the node in the tree */
64 xmlChar *test; /* the expression to test */
65 xmlXPathCompExprPtr comp; /* the compiled expression */
66};
67
68/**
69 * _xmlSchematronRule:
70 *
71 * A Schematrons rule
72 */
73typedef struct _xmlSchematronRule xmlSchematronRule;
74typedef xmlSchematronRule *xmlSchematronRulePtr;
75struct _xmlSchematronRule {
76 xmlSchematronRulePtr next; /* the next rule in the list */
77 xmlNodePtr node; /* the node in the tree */
78 xmlChar *context; /* the context evaluation rule */
79 xmlSchematronTestPtr tests; /* the list of tests */
80 xmlPatternPtr pattern; /* the compiled pattern associated */
81};
82
83/**
84 * _xmlSchematron:
85 *
86 * A Schematrons definition
87 */
88struct _xmlSchematron {
89 const xmlChar *name; /* schema name */
90 int preserve; /* was the document preserved by the user */
91 xmlDocPtr doc; /* pointer to the parsed document */
92 int flags; /* specific to this schematron */
93
94 void *_private; /* unused by the library */
95 xmlDictPtr dict; /* the dictionnary used internally */
96
97 const xmlChar *title; /* the title if any */
98
99 int nbNs; /* the number of namespaces */
100
101 int nbPattern; /* the number of patterns */
102 xmlSchematronRulePtr rules; /* the rules gathered */
103 int nbNamespaces; /* number of namespaces in the array */
104 int maxNamespaces; /* size of the array */
105 const xmlChar **namespaces; /* the array of namespaces */
106};
107
108/**
109 * xmlSchematronValidCtxt:
110 *
111 * A Schematrons validation context
112 */
113struct _xmlSchematronValidCtxt {
114 int type;
115 int flags; /* an or of xmlSchematronValidOptions */
116
117 xmlDictPtr dict;
118 int nberrors;
119 int err;
120
121 xmlSchematronPtr schema;
122 xmlXPathContextPtr xctxt;
123
124 FILE *outputFile; /* if using XML_SCHEMATRON_OUT_FILE */
125 xmlBufferPtr outputBuffer; /* if using XML_SCHEMATRON_OUT_BUFFER */
126 xmlOutputWriteCallback iowrite; /* if using XML_SCHEMATRON_OUT_IO */
127 xmlOutputCloseCallback ioclose;
128 void *ioctx;
129};
130
131struct _xmlSchematronParserCtxt {
132 int type;
133 const xmlChar *URL;
134 xmlDocPtr doc;
135 int preserve; /* Whether the doc should be freed */
136 const char *buffer;
137 int size;
138
139 xmlDictPtr dict; /* dictionnary for interned string names */
140
141 int nberrors;
142 int err;
143 xmlXPathContextPtr xctxt; /* the XPath context used for compilation */
144 xmlSchematronPtr schema;
145
146 int nbNamespaces; /* number of namespaces in the array */
147 int maxNamespaces; /* size of the array */
148 const xmlChar **namespaces; /* the array of namespaces */
149
150 int nbIncludes; /* number of includes in the array */
151 int maxIncludes; /* size of the array */
152 xmlNodePtr *includes; /* the array of includes */
153
154 /* error rreporting data */
155 void *userData; /* user specific data block */
156 xmlSchematronValidityErrorFunc error;/* the callback in case of errors */
157 xmlSchematronValidityWarningFunc warning;/* callback in case of warning */
158 xmlStructuredErrorFunc serror; /* the structured function */
159
160};
161
162#define XML_STRON_CTXT_PARSER 1
163#define XML_STRON_CTXT_VALIDATOR 2
164
165/************************************************************************
166 * *
167 * Error reporting *
168 * *
169 ************************************************************************/
170
171/**
172 * xmlSchematronPErrMemory:
173 * @node: a context node
174 * @extra: extra informations
175 *
176 * Handle an out of memory condition
177 */
178static void
179xmlSchematronPErrMemory(xmlSchematronParserCtxtPtr ctxt,
180 const char *extra, xmlNodePtr node)
181{
182 if (ctxt != NULL)
183 ctxt->nberrors++;
184 __xmlSimpleError(XML_FROM_SCHEMASP, XML_ERR_NO_MEMORY, node, NULL,
185 extra);
186}
187
188/**
189 * xmlSchematronPErr:
190 * @ctxt: the parsing context
191 * @node: the context node
192 * @error: the error code
193 * @msg: the error message
194 * @str1: extra data
195 * @str2: extra data
196 *
197 * Handle a parser error
198 */
199static void
200xmlSchematronPErr(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr node, int error,
201 const char *msg, const xmlChar * str1, const xmlChar * str2)
202{
203 xmlGenericErrorFunc channel = NULL;
204 xmlStructuredErrorFunc schannel = NULL;
205 void *data = NULL;
206
207 if (ctxt != NULL) {
208 ctxt->nberrors++;
209 channel = ctxt->error;
210 data = ctxt->userData;
211 schannel = ctxt->serror;
212 }
213 __xmlRaiseError(schannel, channel, data, ctxt, node, XML_FROM_SCHEMASP,
214 error, XML_ERR_ERROR, NULL, 0,
215 (const char *) str1, (const char *) str2, NULL, 0, 0,
216 msg, str1, str2);
217}
218
219/**
220 * xmlSchematronVTypeErrMemory:
221 * @node: a context node
222 * @extra: extra informations
223 *
224 * Handle an out of memory condition
225 */
226static void
227xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt,
228 const char *extra, xmlNodePtr node)
229{
230 if (ctxt != NULL) {
231 ctxt->nberrors++;
232 ctxt->err = XML_SCHEMAV_INTERNAL;
233 }
234 __xmlSimpleError(XML_FROM_SCHEMASV, XML_ERR_NO_MEMORY, node, NULL,
235 extra);
236}
237
238/************************************************************************
239 * *
240 * Parsing and compilation of the Schematrontrons *
241 * *
242 ************************************************************************/
243
244/**
245 * xmlSchematronAddTest:
246 * @ctxt: the schema parsing context
247 * @schema: a schema structure
248 * @node: the node hosting the test
249 * @context: the associated context string
250 *
251 * Add a test to a schematron
252 *
253 * Returns the new pointer or NULL in case of error
254 */
255static xmlSchematronTestPtr
256xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt, int type,
257 xmlSchematronRulePtr rule,
258 xmlNodePtr node, xmlChar *test)
259{
260 xmlSchematronTestPtr ret;
261 xmlXPathCompExprPtr comp;
262
263 if ((ctxt == NULL) || (rule == NULL) || (node == NULL) ||
264 (test == NULL))
265 return(NULL);
266
267 /*
268 * try first to compile the test expression
269 */
270 comp = xmlXPathCtxtCompile(ctxt->xctxt, test);
271 if (comp == NULL) {
272 xmlSchematronPErr(ctxt, node,
273 XML_SCHEMAP_NOROOT,
274 "Failed to compile test expression %s",
275 test, NULL);
276 return(NULL);
277 }
278
279 ret = (xmlSchematronTestPtr) xmlMalloc(sizeof(xmlSchematronTest));
280 if (ret == NULL) {
281 xmlSchematronPErrMemory(ctxt, "allocating schema test", node);
282 return (NULL);
283 }
284 memset(ret, 0, sizeof(xmlSchematronTest));
285 ret->type = type;
286 ret->node = node;
287 ret->test = test;
288 ret->comp = comp;
289 ret->next = rule->tests;
290 rule->tests = ret;
291 return (ret);
292}
293
294/**
295 * xmlSchematronFreeTests:
296 * @tests: a list of tests
297 *
298 * Free a list of tests.
299 */
300static void
301xmlSchematronFreeTests(xmlSchematronTestPtr tests) {
302 xmlSchematronTestPtr next;
303
304 while (tests != NULL) {
305 next = tests->next;
306 if (tests->test != NULL)
307 xmlFree(tests->test);
308 if (tests->comp != NULL)
309 xmlXPathFreeCompExpr(tests->comp);
310 xmlFree(tests);
311 tests = next;
312 }
313}
314
315/**
316 * xmlSchematronAddRule:
317 * @ctxt: the schema parsing context
318 * @schema: a schema structure
319 * @node: the node hosting the rule
320 * @context: the associated context string
321 *
322 * Add a rule to a schematron
323 *
324 * Returns the new pointer or NULL in case of error
325 */
326static xmlSchematronRulePtr
327xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt, xmlSchematronPtr schema,
328 xmlNodePtr node, xmlChar *context)
329{
330 xmlSchematronRulePtr ret;
331 xmlPatternPtr pattern;
332
333 if ((ctxt == NULL) || (schema == NULL) || (node == NULL) ||
334 (context == NULL))
335 return(NULL);
336
337 /*
338 * Try first to compile the pattern
339 */
340 pattern = xmlPatterncompile(context, ctxt->dict, XML_PATTERN_XPATH,
341 ctxt->namespaces);
342 if (pattern == NULL) {
343 xmlSchematronPErr(ctxt, node,
344 XML_SCHEMAP_NOROOT,
345 "Failed to compile context expression %s",
346 context, NULL);
347 }
348
349 ret = (xmlSchematronRulePtr) xmlMalloc(sizeof(xmlSchematronRule));
350 if (ret == NULL) {
351 xmlSchematronPErrMemory(ctxt, "allocating schema rule", node);
352 return (NULL);
353 }
354 memset(ret, 0, sizeof(xmlSchematronRule));
355 ret->node = node;
356 ret->context = context;
357 ret->next = schema->rules;
358 ret->pattern = pattern;
359 schema->rules = ret;
360 return (ret);
361}
362
363/**
364 * xmlSchematronFreeRules:
365 * @rules: a list of rules
366 *
367 * Free a list of rules.
368 */
369static void
370xmlSchematronFreeRules(xmlSchematronRulePtr rules) {
371 xmlSchematronRulePtr next;
372
373 while (rules != NULL) {
374 next = rules->next;
375 if (rules->tests)
376 xmlSchematronFreeTests(rules->tests);
377 if (rules->context != NULL)
378 xmlFree(rules->context);
379 if (rules->pattern)
380 xmlFreePattern(rules->pattern);
381 xmlFree(rules);
382 rules = next;
383 }
384}
385
386/**
387 * xmlSchematronNewSchematron:
388 * @ctxt: a schema validation context
389 *
390 * Allocate a new Schematron structure.
391 *
392 * Returns the newly allocated structure or NULL in case or error
393 */
394static xmlSchematronPtr
395xmlSchematronNewSchematron(xmlSchematronParserCtxtPtr ctxt)
396{
397 xmlSchematronPtr ret;
398
399 ret = (xmlSchematronPtr) xmlMalloc(sizeof(xmlSchematron));
400 if (ret == NULL) {
401 xmlSchematronPErrMemory(ctxt, "allocating schema", NULL);
402 return (NULL);
403 }
404 memset(ret, 0, sizeof(xmlSchematron));
405 ret->dict = ctxt->dict;
406 xmlDictReference(ret->dict);
407
408 return (ret);
409}
410
411/**
412 * xmlSchematronFree:
413 * @schema: a schema structure
414 *
415 * Deallocate a Schematron structure.
416 */
417void
418xmlSchematronFree(xmlSchematronPtr schema)
419{
420 if (schema == NULL)
421 return;
422
423 if ((schema->doc != NULL) && (!(schema->preserve)))
424 xmlFreeDoc(schema->doc);
425
426 if (schema->namespaces != NULL)
427 xmlFree(schema->namespaces);
428
429 xmlSchematronFreeRules(schema->rules);
430 xmlDictFree(schema->dict);
431 xmlFree(schema);
432}
433
434/**
435 * xmlSchematronNewParserCtxt:
436 * @URL: the location of the schema
437 *
438 * Create an XML Schematrons parse context for that file/resource expected
439 * to contain an XML Schematrons file.
440 *
441 * Returns the parser context or NULL in case of error
442 */
443xmlSchematronParserCtxtPtr
444xmlSchematronNewParserCtxt(const char *URL)
445{
446 xmlSchematronParserCtxtPtr ret;
447
448 if (URL == NULL)
449 return (NULL);
450
451 ret =
452 (xmlSchematronParserCtxtPtr)
453 xmlMalloc(sizeof(xmlSchematronParserCtxt));
454 if (ret == NULL) {
455 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
456 NULL);
457 return (NULL);
458 }
459 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
460 ret->type = XML_STRON_CTXT_PARSER;
461 ret->dict = xmlDictCreate();
462 ret->URL = xmlDictLookup(ret->dict, (const xmlChar *) URL, -1);
463 ret->includes = 0;
464 ret->xctxt = xmlXPathNewContext(NULL);
465 if (ret->xctxt == NULL) {
466 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
467 NULL);
468 xmlSchematronFreeParserCtxt(ret);
469 return (NULL);
470 }
471 ret->xctxt->flags = XML_XPATH_CHECKNS;
472 return (ret);
473}
474
475/**
476 * xmlSchematronNewMemParserCtxt:
477 * @buffer: a pointer to a char array containing the schemas
478 * @size: the size of the array
479 *
480 * Create an XML Schematrons parse context for that memory buffer expected
481 * to contain an XML Schematrons file.
482 *
483 * Returns the parser context or NULL in case of error
484 */
485xmlSchematronParserCtxtPtr
486xmlSchematronNewMemParserCtxt(const char *buffer, int size)
487{
488 xmlSchematronParserCtxtPtr ret;
489
490 if ((buffer == NULL) || (size <= 0))
491 return (NULL);
492
493 ret =
494 (xmlSchematronParserCtxtPtr)
495 xmlMalloc(sizeof(xmlSchematronParserCtxt));
496 if (ret == NULL) {
497 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
498 NULL);
499 return (NULL);
500 }
501 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
502 ret->buffer = buffer;
503 ret->size = size;
504 ret->dict = xmlDictCreate();
505 ret->xctxt = xmlXPathNewContext(NULL);
506 if (ret->xctxt == NULL) {
507 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
508 NULL);
509 xmlSchematronFreeParserCtxt(ret);
510 return (NULL);
511 }
512 return (ret);
513}
514
515/**
516 * xmlSchematronNewDocParserCtxt:
517 * @doc: a preparsed document tree
518 *
519 * Create an XML Schematrons parse context for that document.
520 * NB. The document may be modified during the parsing process.
521 *
522 * Returns the parser context or NULL in case of error
523 */
524xmlSchematronParserCtxtPtr
525xmlSchematronNewDocParserCtxt(xmlDocPtr doc)
526{
527 xmlSchematronParserCtxtPtr ret;
528
529 if (doc == NULL)
530 return (NULL);
531
532 ret =
533 (xmlSchematronParserCtxtPtr)
534 xmlMalloc(sizeof(xmlSchematronParserCtxt));
535 if (ret == NULL) {
536 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
537 NULL);
538 return (NULL);
539 }
540 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
541 ret->doc = doc;
542 ret->dict = xmlDictCreate();
543 /* The application has responsibility for the document */
544 ret->preserve = 1;
545 ret->xctxt = xmlXPathNewContext(doc);
546 if (ret->xctxt == NULL) {
547 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
548 NULL);
549 xmlSchematronFreeParserCtxt(ret);
550 return (NULL);
551 }
552
553 return (ret);
554}
555
556/**
557 * xmlSchematronFreeParserCtxt:
558 * @ctxt: the schema parser context
559 *
560 * Free the resources associated to the schema parser context
561 */
562void
563xmlSchematronFreeParserCtxt(xmlSchematronParserCtxtPtr ctxt)
564{
565 if (ctxt == NULL)
566 return;
567 if (ctxt->doc != NULL && !ctxt->preserve)
568 xmlFreeDoc(ctxt->doc);
569 if (ctxt->xctxt != NULL) {
570 xmlXPathFreeContext(ctxt->xctxt);
571 }
572 if (ctxt->namespaces != NULL)
573 xmlFree(ctxt->namespaces);
574 xmlDictFree(ctxt->dict);
575 xmlFree(ctxt);
576}
577
578/**
579 * xmlSchematronPushInclude:
580 * @ctxt: the schema parser context
581 * @doc: the included document
582 * @cur: the current include node
583 *
584 * Add an included document
585 */
586static void
587xmlSchematronPushInclude(xmlSchematronParserCtxtPtr ctxt,
588 xmlDocPtr doc, xmlNodePtr cur)
589{
590 if (ctxt->includes == NULL) {
591 ctxt->maxIncludes = 10;
592 ctxt->includes = (xmlNodePtr *)
593 xmlMalloc(ctxt->maxIncludes * 2 * sizeof(xmlNodePtr));
594 if (ctxt->includes == NULL) {
595 xmlSchematronPErrMemory(NULL, "allocating parser includes",
596 NULL);
597 return;
598 }
599 ctxt->nbIncludes = 0;
600 } else if (ctxt->nbIncludes + 2 >= ctxt->maxIncludes) {
601 xmlNodePtr *tmp;
602
603 tmp = (xmlNodePtr *)
604 xmlRealloc(ctxt->includes, ctxt->maxIncludes * 4 *
605 sizeof(xmlNodePtr));
606 if (tmp == NULL) {
607 xmlSchematronPErrMemory(NULL, "allocating parser includes",
608 NULL);
609 return;
610 }
611 ctxt->includes = tmp;
612 ctxt->maxIncludes *= 2;
613 }
614 ctxt->includes[2 * ctxt->nbIncludes] = cur;
615 ctxt->includes[2 * ctxt->nbIncludes + 1] = (xmlNodePtr) doc;
616 ctxt->nbIncludes++;
617}
618
619/**
620 * xmlSchematronPopInclude:
621 * @ctxt: the schema parser context
622 *
623 * Pop an include level. The included document is being freed
624 *
625 * Returns the node immediately following the include or NULL if the
626 * include list was empty.
627 */
628static xmlNodePtr
629xmlSchematronPopInclude(xmlSchematronParserCtxtPtr ctxt)
630{
631 xmlDocPtr doc;
632 xmlNodePtr ret;
633
634 if (ctxt->nbIncludes <= 0)
635 return(NULL);
636 ctxt->nbIncludes--;
637 doc = (xmlDocPtr) ctxt->includes[2 * ctxt->nbIncludes + 1];
638 ret = ctxt->includes[2 * ctxt->nbIncludes];
639 xmlFreeDoc(doc);
640 if (ret != NULL)
641 ret = ret->next;
642 if (ret == NULL)
643 return(xmlSchematronPopInclude(ctxt));
644 return(ret);
645}
646
647/**
648 * xmlSchematronAddNamespace:
649 * @ctxt: the schema parser context
650 * @prefix: the namespace prefix
651 * @ns: the namespace name
652 *
653 * Add a namespace definition in the context
654 */
655static void
656xmlSchematronAddNamespace(xmlSchematronParserCtxtPtr ctxt,
657 const xmlChar *prefix, const xmlChar *ns)
658{
659 if (ctxt->namespaces == NULL) {
660 ctxt->maxNamespaces = 10;
661 ctxt->namespaces = (const xmlChar **)
662 xmlMalloc(ctxt->maxNamespaces * 2 * sizeof(const xmlChar *));
663 if (ctxt->namespaces == NULL) {
664 xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
665 NULL);
666 return;
667 }
668 ctxt->nbNamespaces = 0;
669 } else if (ctxt->nbNamespaces + 2 >= ctxt->maxNamespaces) {
670 const xmlChar **tmp;
671
672 tmp = (const xmlChar **)
673 xmlRealloc(ctxt->namespaces, ctxt->maxNamespaces * 4 *
674 sizeof(const xmlChar *));
675 if (tmp == NULL) {
676 xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
677 NULL);
678 return;
679 }
680 ctxt->namespaces = tmp;
681 ctxt->maxNamespaces *= 2;
682 }
683 ctxt->namespaces[2 * ctxt->nbNamespaces] =
684 xmlDictLookup(ctxt->dict, ns, -1);
685 ctxt->namespaces[2 * ctxt->nbNamespaces + 1] =
686 xmlDictLookup(ctxt->dict, prefix, -1);
687 ctxt->nbNamespaces++;
688 ctxt->namespaces[2 * ctxt->nbNamespaces] = NULL;
689 ctxt->namespaces[2 * ctxt->nbNamespaces + 1] = NULL;
690
691}
692
693/**
694 * xmlSchematronParseRule:
695 * @ctxt: a schema validation context
696 * @rule: the rule node
697 *
698 * parse a rule element
699 */
700static void
701xmlSchematronParseRule(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr rule)
702{
703 xmlNodePtr cur;
704 int nbChecks = 0;
705 xmlChar *test;
706 xmlChar *context;
707 xmlSchematronRulePtr ruleptr;
708 xmlSchematronTestPtr testptr;
709
710 if ((ctxt == NULL) || (rule == NULL)) return;
711
712 context = xmlGetNoNsProp(rule, BAD_CAST "context");
713 if (context == NULL) {
714 xmlSchematronPErr(ctxt, rule,
715 XML_SCHEMAP_NOROOT,
716 "rule has no context attribute",
717 NULL, NULL);
718 return;
719 } else if (context[0] == 0) {
720 xmlSchematronPErr(ctxt, rule,
721 XML_SCHEMAP_NOROOT,
722 "rule has an empty context attribute",
723 NULL, NULL);
724 xmlFree(context);
725 return;
726 } else {
727 ruleptr = xmlSchematronAddRule(ctxt, ctxt->schema, rule, context);
728 if (ruleptr == NULL) {
729 xmlFree(context);
730 return;
731 }
732 }
733
734 cur = rule->children;
735 NEXT_SCHEMATRON(cur);
736 while (cur != NULL) {
737 if (IS_SCHEMATRON(cur, "assert")) {
738 nbChecks++;
739 test = xmlGetNoNsProp(cur, BAD_CAST "test");
740 if (test == NULL) {
741 xmlSchematronPErr(ctxt, cur,
742 XML_SCHEMAP_NOROOT,
743 "assert has no test attribute",
744 NULL, NULL);
745 } else if (test[0] == 0) {
746 xmlSchematronPErr(ctxt, cur,
747 XML_SCHEMAP_NOROOT,
748 "assert has an empty test attribute",
749 NULL, NULL);
750 xmlFree(test);
751 } else {
752 testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_ASSERT,
753 ruleptr, cur, test);
754 if (testptr == NULL)
755 xmlFree(test);
756 }
757 } else if (IS_SCHEMATRON(cur, "report")) {
758 nbChecks++;
759 test = xmlGetNoNsProp(cur, BAD_CAST "test");
760 if (test == NULL) {
761 xmlSchematronPErr(ctxt, cur,
762 XML_SCHEMAP_NOROOT,
763 "assert has no test attribute",
764 NULL, NULL);
765 } else if (test[0] == 0) {
766 xmlSchematronPErr(ctxt, cur,
767 XML_SCHEMAP_NOROOT,
768 "assert has an empty test attribute",
769 NULL, NULL);
770 xmlFree(test);
771 } else {
772 testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_REPORT,
773 ruleptr, cur, test);
774 if (testptr == NULL)
775 xmlFree(test);
776 }
777 } else {
778 xmlSchematronPErr(ctxt, cur,
779 XML_SCHEMAP_NOROOT,
780 "Expecting an assert or a report element instead of %s",
781 cur->name, NULL);
782 }
783 cur = cur->next;
784 NEXT_SCHEMATRON(cur);
785 }
786 if (nbChecks == 0) {
787 xmlSchematronPErr(ctxt, rule,
788 XML_SCHEMAP_NOROOT,
789 "rule has no assert nor report element", NULL, NULL);
790 }
791}
792
793/**
794 * xmlSchematronParsePattern:
795 * @ctxt: a schema validation context
796 * @pat: the pattern node
797 *
798 * parse a pattern element
799 */
800static void
801xmlSchematronParsePattern(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr pat)
802{
803 xmlNodePtr cur;
804 int nbRules = 0;
805
806 if ((ctxt == NULL) || (pat == NULL)) return;
807
808 cur = pat->children;
809 NEXT_SCHEMATRON(cur);
810 while (cur != NULL) {
811 if (IS_SCHEMATRON(cur, "rule")) {
812 xmlSchematronParseRule(ctxt, cur);
813 nbRules++;
814 } else {
815 xmlSchematronPErr(ctxt, cur,
816 XML_SCHEMAP_NOROOT,
817 "Expecting a rule element instead of %s", cur->name, NULL);
818 }
819 cur = cur->next;
820 NEXT_SCHEMATRON(cur);
821 }
822 if (nbRules == 0) {
823 xmlSchematronPErr(ctxt, pat,
824 XML_SCHEMAP_NOROOT,
825 "Pattern has no rule element", NULL, NULL);
826 }
827}
828
829/**
830 * xmlSchematronLoadInclude:
831 * @ctxt: a schema validation context
832 * @cur: the include element
833 *
834 * Load the include document, Push the current pointer
835 *
836 * Returns the updated node pointer
837 */
838static xmlNodePtr
839xmlSchematronLoadInclude(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr cur)
840{
841 xmlNodePtr ret = NULL;
842 xmlDocPtr doc = NULL;
843 xmlChar *href = NULL;
844 xmlChar *base = NULL;
845 xmlChar *URI = NULL;
846
847 if ((ctxt == NULL) || (cur == NULL))
848 return(NULL);
849
850 href = xmlGetNoNsProp(cur, BAD_CAST "href");
851 if (href == NULL) {
852 xmlSchematronPErr(ctxt, cur,
853 XML_SCHEMAP_NOROOT,
854 "Include has no href attribute", NULL, NULL);
855 return(cur->next);
856 }
857
858 /* do the URI base composition, load and find the root */
859 base = xmlNodeGetBase(cur->doc, cur);
860 URI = xmlBuildURI(href, base);
861 doc = xmlReadFile((const char *) URI, NULL, SCHEMATRON_PARSE_OPTIONS);
862 if (doc == NULL) {
863 xmlSchematronPErr(ctxt, cur,
864 XML_SCHEMAP_FAILED_LOAD,
865 "could not load include '%s'.\n",
866 URI, NULL);
867 goto done;
868 }
869 ret = xmlDocGetRootElement(doc);
870 if (ret == NULL) {
871 xmlSchematronPErr(ctxt, cur,
872 XML_SCHEMAP_FAILED_LOAD,
873 "could not find root from include '%s'.\n",
874 URI, NULL);
875 goto done;
876 }
877
878 /* Success, push the include for rollback on exit */
879 xmlSchematronPushInclude(ctxt, doc, cur);
880
881done:
882 if (ret == NULL) {
883 if (doc != NULL)
884 xmlFreeDoc(doc);
885 }
886 if (href == NULL)
887 xmlFree(href);
888 if (base == NULL)
889 xmlFree(base);
890 if (URI == NULL)
891 xmlFree(URI);
892 return(ret);
893}
894
895/**
896 * xmlSchematronParse:
897 * @ctxt: a schema validation context
898 *
899 * parse a schema definition resource and build an internal
900 * XML Shema struture which can be used to validate instances.
901 *
902 * Returns the internal XML Schematron structure built from the resource or
903 * NULL in case of error
904 */
905xmlSchematronPtr
906xmlSchematronParse(xmlSchematronParserCtxtPtr ctxt)
907{
908 xmlSchematronPtr ret = NULL;
909 xmlDocPtr doc;
910 xmlNodePtr root, cur;
911 int preserve = 0;
912
913 if (ctxt == NULL)
914 return (NULL);
915
916 ctxt->nberrors = 0;
917
918 /*
919 * First step is to parse the input document into an DOM/Infoset
920 */
921 if (ctxt->URL != NULL) {
922 doc = xmlReadFile((const char *) ctxt->URL, NULL,
923 SCHEMATRON_PARSE_OPTIONS);
924 if (doc == NULL) {
925 xmlSchematronPErr(ctxt, NULL,
926 XML_SCHEMAP_FAILED_LOAD,
927 "xmlSchematronParse: could not load '%s'.\n",
928 ctxt->URL, NULL);
929 return (NULL);
930 }
931 } else if (ctxt->buffer != NULL) {
932 doc = xmlReadMemory(ctxt->buffer, ctxt->size, NULL, NULL,
933 SCHEMATRON_PARSE_OPTIONS);
934 if (doc == NULL) {
935 xmlSchematronPErr(ctxt, NULL,
936 XML_SCHEMAP_FAILED_PARSE,
937 "xmlSchematronParse: could not parse.\n",
938 NULL, NULL);
939 return (NULL);
940 }
941 doc->URL = xmlStrdup(BAD_CAST "in_memory_buffer");
942 ctxt->URL = xmlDictLookup(ctxt->dict, BAD_CAST "in_memory_buffer", -1);
943 } else if (ctxt->doc != NULL) {
944 doc = ctxt->doc;
945 preserve = 1;
946 } else {
947 xmlSchematronPErr(ctxt, NULL,
948 XML_SCHEMAP_NOTHING_TO_PARSE,
949 "xmlSchematronParse: could not parse.\n",
950 NULL, NULL);
951 return (NULL);
952 }
953
954 /*
955 * Then extract the root and Schematron parse it
956 */
957 root = xmlDocGetRootElement(doc);
958 if (root == NULL) {
959 xmlSchematronPErr(ctxt, (xmlNodePtr) doc,
960 XML_SCHEMAP_NOROOT,
961 "The schema has no document element.\n", NULL, NULL);
962 if (!preserve) {
963 xmlFreeDoc(doc);
964 }
965 return (NULL);
966 }
967
968 if (!IS_SCHEMATRON(root, "schema")) {
969 xmlSchematronPErr(ctxt, root,
970 XML_SCHEMAP_NOROOT,
971 "The XML document '%s' is not a XML schematron document",
972 ctxt->URL, NULL);
973 goto exit;
974 }
975 ret = xmlSchematronNewSchematron(ctxt);
976 if (ret == NULL)
977 goto exit;
978 ctxt->schema = ret;
979
980 /*
981 * scan the schema elements
982 */
983 cur = root->children;
984 NEXT_SCHEMATRON(cur);
985 if (IS_SCHEMATRON(cur, "title")) {
986 xmlChar *title = xmlNodeGetContent(cur);
987 if (title != NULL) {
988 ret->title = xmlDictLookup(ret->dict, title, -1);
989 xmlFree(title);
990 }
991 cur = cur->next;
992 NEXT_SCHEMATRON(cur);
993 }
994 while (IS_SCHEMATRON(cur, "ns")) {
995 xmlChar *prefix = xmlGetNoNsProp(cur, BAD_CAST "prefix");
996 xmlChar *uri = xmlGetNoNsProp(cur, BAD_CAST "uri");
997 if ((uri == NULL) || (uri[0] == 0)) {
998 xmlSchematronPErr(ctxt, cur,
999 XML_SCHEMAP_NOROOT,
1000 "ns element has no uri", NULL, NULL);
1001 }
1002 if ((prefix == NULL) || (prefix[0] == 0)) {
1003 xmlSchematronPErr(ctxt, cur,
1004 XML_SCHEMAP_NOROOT,
1005 "ns element has no prefix", NULL, NULL);
1006 }
1007 if ((prefix) && (uri)) {
1008 xmlXPathRegisterNs(ctxt->xctxt, prefix, uri);
1009 xmlSchematronAddNamespace(ctxt, prefix, uri);
1010 ret->nbNs++;
1011 }
1012 if (uri)
1013 xmlFree(uri);
1014 if (prefix)
1015 xmlFree(prefix);
1016 cur = cur->next;
1017 NEXT_SCHEMATRON(cur);
1018 }
1019 while (cur != NULL) {
1020 if (IS_SCHEMATRON(cur, "pattern")) {
1021 xmlSchematronParsePattern(ctxt, cur);
1022 ret->nbPattern++;
1023 } else {
1024 xmlSchematronPErr(ctxt, cur,
1025 XML_SCHEMAP_NOROOT,
1026 "Expecting a pattern element instead of %s", cur->name, NULL);
1027 }
1028 cur = cur->next;
1029 NEXT_SCHEMATRON(cur);
1030 }
1031 if (ret->nbPattern == 0) {
1032 xmlSchematronPErr(ctxt, root,
1033 XML_SCHEMAP_NOROOT,
1034 "The schematron document '%s' has no pattern",
1035 ctxt->URL, NULL);
1036 goto exit;
1037 }
1038
1039exit:
1040 if (!preserve) {
1041 xmlFreeDoc(doc);
1042 }
1043 if (ctxt->nberrors != 0) {
1044 xmlSchematronFree(ret);
1045 ret = NULL;
1046 } else {
1047 ret->namespaces = ctxt->namespaces;
1048 ret->nbNamespaces = ctxt->nbNamespaces;
1049 ctxt->namespaces = NULL;
1050 }
1051 return (ret);
1052}
1053
1054/************************************************************************
1055 * *
1056 * Schematrontron Reports handler *
1057 * *
1058 ************************************************************************/
1059
1060static void
1061xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt,
1062 xmlSchematronTestPtr test, xmlNodePtr cur) {
1063}
1064
1065/************************************************************************
1066 * *
1067 * Validation against a Schematrontron *
1068 * *
1069 ************************************************************************/
1070
1071/**
1072 * xmlSchematronNewValidCtxt:
1073 * @schema: a precompiled XML Schematrons
1074 * @options: a set of xmlSchematronValidOptions
1075 *
1076 * Create an XML Schematrons validation context based on the given schema.
1077 *
1078 * Returns the validation context or NULL in case of error
1079 */
1080xmlSchematronValidCtxtPtr
1081xmlSchematronNewValidCtxt(xmlSchematronPtr schema, int options)
1082{
1083 int i;
1084 xmlSchematronValidCtxtPtr ret;
1085
1086 ret =
1087 (xmlSchematronValidCtxtPtr)
1088 xmlMalloc(sizeof(xmlSchematronValidCtxt));
1089 if (ret == NULL) {
1090 xmlSchematronVErrMemory(NULL, "allocating validation context",
1091 NULL);
1092 return (NULL);
1093 }
1094 memset(ret, 0, sizeof(xmlSchematronValidCtxt));
1095 ret->type = XML_STRON_CTXT_VALIDATOR;
1096 ret->schema = schema;
1097 ret->xctxt = xmlXPathNewContext(NULL);
1098 if (ret->xctxt == NULL) {
1099 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
1100 NULL);
1101 xmlSchematronFreeValidCtxt(ret);
1102 return (NULL);
1103 }
1104 for (i = 0;i < schema->nbNamespaces;i++) {
1105 if ((schema->namespaces[2 * i] == NULL) ||
1106 (schema->namespaces[2 * i + 1] == NULL))
1107 break;
1108 xmlXPathRegisterNs(ret->xctxt, schema->namespaces[2 * i + 1],
1109 schema->namespaces[2 * i]);
1110 }
1111 return (ret);
1112}
1113
1114/**
1115 * xmlSchematronFreeValidCtxt:
1116 * @ctxt: the schema validation context
1117 *
1118 * Free the resources associated to the schema validation context
1119 */
1120void
1121xmlSchematronFreeValidCtxt(xmlSchematronValidCtxtPtr ctxt)
1122{
1123 if (ctxt == NULL)
1124 return;
1125 if (ctxt->xctxt != NULL)
1126 xmlXPathFreeContext(ctxt->xctxt);
1127 if (ctxt->dict != NULL)
1128 xmlDictFree(ctxt->dict);
1129 xmlFree(ctxt);
1130}
1131
1132static xmlNodePtr
1133xmlSchematronNextNode(xmlNodePtr cur) {
1134 if (cur->children != NULL) {
1135 /*
1136 * Do not descend on entities declarations
1137 */
1138 if (cur->children->type != XML_ENTITY_DECL) {
1139 cur = cur->children;
1140 /*
1141 * Skip DTDs
1142 */
1143 if (cur->type != XML_DTD_NODE)
1144 return(cur);
1145 }
1146 }
1147
1148 while (cur->next != NULL) {
1149 cur = cur->next;
1150 if ((cur->type != XML_ENTITY_DECL) &&
1151 (cur->type != XML_DTD_NODE))
1152 return(cur);
1153 }
1154
1155 do {
1156 cur = cur->parent;
1157 if (cur == NULL) return(NULL);
1158 if (cur->type == XML_DOCUMENT_NODE) return(NULL);
1159 if (cur->next != NULL) {
1160 cur = cur->next;
1161 return(cur);
1162 }
1163 } while (cur != NULL);
1164 return(cur);
1165}
1166
1167/**
1168 * xmlSchematronRunTest:
1169 * @ctxt: the schema validation context
1170 * @test: the current test
1171 * @instance: the document instace tree
1172 * @cur: the current node in the instance
1173 *
1174 * Validate a rule against a tree instance at a given position
1175 *
1176 * Returns 1 in case of success, 0 if error and -1 in case of internal error
1177 */
1178static int
1179xmlSchematronRunTest(xmlSchematronValidCtxtPtr ctxt,
1180 xmlSchematronTestPtr test, xmlDocPtr instance, xmlNodePtr cur)
1181{
1182 xmlXPathObjectPtr ret;
1183 int failed;
1184
1185 failed = 0;
1186 ctxt->xctxt->doc = instance;
1187 ctxt->xctxt->node = cur;
1188 ret = xmlXPathCompiledEval(test->comp, ctxt->xctxt);
1189 if (ret == NULL) {
1190 failed = 1;
1191 } else switch (ret->type) {
1192 case XPATH_XSLT_TREE:
1193 case XPATH_NODESET:
1194 if ((ret->nodesetval == NULL) ||
1195 (ret->nodesetval->nodeNr == 0))
1196 failed = 1;
1197 break;
1198 case XPATH_BOOLEAN:
1199 failed = !ret->boolval;
1200 break;
1201 case XPATH_NUMBER:
1202 if ((xmlXPathIsNaN(ret->floatval)) ||
1203 (ret->floatval == 0.0))
1204 failed = 1;
1205 break;
1206 case XPATH_STRING:
1207 if ((ret->stringval == NULL) ||
1208 (ret->stringval[0] == 0))
1209 failed = 1;
1210 break;
1211 case XPATH_UNDEFINED:
1212 case XPATH_POINT:
1213 case XPATH_RANGE:
1214 case XPATH_LOCATIONSET:
1215 case XPATH_USERS:
1216 failed = 1;
1217 break;
1218 }
1219 if (test->type == XML_SCHEMATRON_REPORT) {
1220 if (!failed) {
1221 printf("report failed\n");
1222 }
1223 } else {
1224 if (failed) {
1225 printf("assert failed\n");
1226 }
1227 }
1228
1229 return(!failed);
1230}
1231
1232/**
1233 * xmlSchematronValidateDoc:
1234 * @ctxt: the schema validation context
1235 * @instance: the document instace tree
1236 *
1237 * Validate a tree instance against the schematron
1238 *
1239 * Returns 0 in case of success, -1 in case of internal error
1240 * and an error count otherwise.
1241 */
1242int
1243xmlSchematronValidateDoc(xmlSchematronValidCtxtPtr ctxt, xmlDocPtr instance)
1244{
1245 xmlNodePtr cur;
1246 xmlSchematronRulePtr rule;
1247 xmlSchematronTestPtr test;
1248
1249 if ((ctxt == NULL) || (ctxt->schema == NULL) ||
1250 (ctxt->schema->rules == NULL) || (instance == NULL))
1251 return(-1);
1252 ctxt->nberrors = 0;
1253 cur = xmlDocGetRootElement(instance);
1254 while (cur != NULL) {
1255 rule = ctxt->schema->rules;
1256 while (rule != NULL) {
1257 if (xmlPatternMatch(rule->pattern, cur) == 1) {
1258 printf("%s matches\n", cur->name);
1259 test = rule->tests;
1260 while (test != NULL) {
1261 xmlSchematronRunTest(ctxt, test, instance, cur);
1262 test = test->next;
1263 }
1264 }
1265 rule = rule->next;
1266 }
1267
1268 cur = xmlSchematronNextNode(cur);
1269 }
1270 return(ctxt->nberrors);
1271}
1272
1273#ifdef STANDALONE
1274int
1275main(void)
1276{
1277 int ret;
1278 xmlDocPtr instance;
1279 xmlSchematronParserCtxtPtr pctxt;
1280 xmlSchematronValidCtxtPtr vctxt;
1281 xmlSchematronPtr schema = NULL;
1282
1283 pctxt = xmlSchematronNewParserCtxt("tst.sct");
1284 if (pctxt == NULL) {
1285 fprintf(stderr, "failed to build schematron parser\n");
1286 } else {
1287 schema = xmlSchematronParse(pctxt);
1288 if (schema == NULL) {
1289 fprintf(stderr, "failed to compile schematron\n");
1290 }
1291 xmlSchematronFreeParserCtxt(pctxt);
1292 }
1293 instance = xmlReadFile("tst.sct", NULL,
1294 XML_PARSE_NOENT | XML_PARSE_NOCDATA);
1295 if (instance == NULL) {
1296 fprintf(stderr, "failed to parse instance\n");
1297 }
1298 if ((schema != NULL) && (instance != NULL)) {
1299 vctxt = xmlSchematronNewValidCtxt(schema);
1300 if (vctxt == NULL) {
1301 fprintf(stderr, "failed to build schematron validator\n");
1302 } else {
1303 ret = xmlSchematronValidateDoc(vctxt, instance);
1304 xmlSchematronFreeValidCtxt(vctxt);
1305 }
1306 }
1307 xmlSchematronFree(schema);
1308 xmlFreeDoc(instance);
1309
1310 xmlCleanupParser();
1311 xmlMemoryDump();
1312
1313 return (0);
1314}
1315#endif
1316#endif /* LIBXML_SCHEMATRON_ENABLED */