1 /*
2 * schematron.c : implementation of the Schematron schema validity checking
3 *
4 * See Copyright for the status of this software.
5 *
6 * Daniel Veillard <daniel@veillard.com>
7 */
8
9 /*
10 * TODO:
11 * + double check the semantic, especially
12 * - multiple rules applying in a single pattern/node
13 * - the semantic of libxml2 patterns vs. XSLT production referenced
14 * by the spec.
15 * + export of results in SVRL
16 * + full parsing and coverage of the spec, conformance of the input to the
17 * spec
18 * + divergences between the draft and the ISO proposed standard :-(
19 * + hook and test include
20 * + try and compare with the XSLT version
21 */
22
23 #define IN_LIBXML
24 #include "libxml.h"
25
26 #ifdef LIBXML_SCHEMATRON_ENABLED
27
28 #include <string.h>
29 #include <libxml/parser.h>
30 #include <libxml/tree.h>
31 #include <libxml/uri.h>
32 #include <libxml/xpath.h>
33 #include <libxml/xpathInternals.h>
34 #include <libxml/pattern.h>
35 #include <libxml/schematron.h>
36
37 #define SCHEMATRON_PARSE_OPTIONS XML_PARSE_NOENT
38
39 #define SCT_OLD_NS BAD_CAST "http://www.ascc.net/xml/schematron"
40
41 #define XML_SCHEMATRON_NS BAD_CAST "http://purl.oclc.org/dsdl/schematron"
42
43
44 static const xmlChar *xmlSchematronNs = XML_SCHEMATRON_NS;
45 static const xmlChar *xmlOldSchematronNs = SCT_OLD_NS;
46
47 #define IS_SCHEMATRON(node, elem) \
48 ((node != NULL) && (node->type == XML_ELEMENT_NODE ) && \
49 (node->ns != NULL) && \
50 (xmlStrEqual(node->name, (const xmlChar *) elem)) && \
51 ((xmlStrEqual(node->ns->href, xmlSchematronNs)) || \
52 (xmlStrEqual(node->ns->href, xmlOldSchematronNs))))
53
54 #define NEXT_SCHEMATRON(node) \
55 while (node != NULL) { \
56 if ((node->type == XML_ELEMENT_NODE ) && (node->ns != NULL) && \
57 ((xmlStrEqual(node->ns->href, xmlSchematronNs)) || \
58 (xmlStrEqual(node->ns->href, xmlOldSchematronNs)))) \
59 break; \
60 node = node->next; \
61 }
62
63 /**
64 * TODO:
65 *
66 * macro to flag unimplemented blocks
67 */
68 #define TODO \
69 xmlGenericError(xmlGenericErrorContext, \
70 "Unimplemented block at %s:%d\n", \
71 __FILE__, __LINE__);
72
73 typedef enum {
74 XML_SCHEMATRON_ASSERT=1,
75 XML_SCHEMATRON_REPORT=2
76 } xmlSchematronTestType;
77
78 /**
79 * _xmlSchematronTest:
80 *
81 * A Schematrons test, either an assert or a report
82 */
83 typedef struct _xmlSchematronTest xmlSchematronTest;
84 typedef xmlSchematronTest *xmlSchematronTestPtr;
85 struct _xmlSchematronTest {
86 xmlSchematronTestPtr next; /* the next test in the list */
87 xmlSchematronTestType type; /* the test type */
88 xmlNodePtr node; /* the node in the tree */
89 xmlChar *test; /* the expression to test */
90 xmlXPathCompExprPtr comp; /* the compiled expression */
91 xmlChar *report; /* the message to report */
92 };
93
94 /**
95 * _xmlSchematronRule:
96 *
97 * A Schematrons rule
98 */
99 typedef struct _xmlSchematronRule xmlSchematronRule;
100 typedef xmlSchematronRule *xmlSchematronRulePtr;
101 struct _xmlSchematronRule {
102 xmlSchematronRulePtr next; /* the next rule in the list */
103 xmlSchematronRulePtr patnext;/* the next rule in the pattern list */
104 xmlNodePtr node; /* the node in the tree */
105 xmlChar *context; /* the context evaluation rule */
106 xmlSchematronTestPtr tests; /* the list of tests */
107 xmlPatternPtr pattern; /* the compiled pattern associated */
108 xmlChar *report; /* the message to report */
109 };
110
111 /**
112 * _xmlSchematronPattern:
113 *
114 * A Schematrons pattern
115 */
116 typedef struct _xmlSchematronPattern xmlSchematronPattern;
117 typedef xmlSchematronPattern *xmlSchematronPatternPtr;
118 struct _xmlSchematronPattern {
119 xmlSchematronPatternPtr next;/* the next pattern in the list */
120 xmlSchematronRulePtr rules; /* the list of rules */
121 xmlChar *name; /* the name of the pattern */
122 };
123
124 /**
125 * _xmlSchematron:
126 *
127 * A Schematrons definition
128 */
129 struct _xmlSchematron {
130 const xmlChar *name; /* schema name */
131 int preserve; /* was the document passed by the user */
132 xmlDocPtr doc; /* pointer to the parsed document */
133 int flags; /* specific to this schematron */
134
135 void *_private; /* unused by the library */
136 xmlDictPtr dict; /* the dictionnary used internally */
137
138 const xmlChar *title; /* the title if any */
139
140 int nbNs; /* the number of namespaces */
141
142 int nbPattern; /* the number of patterns */
143 xmlSchematronPatternPtr patterns;/* the patterns found */
144 xmlSchematronRulePtr rules; /* the rules gathered */
145 int nbNamespaces; /* number of namespaces in the array */
146 int maxNamespaces; /* size of the array */
147 const xmlChar **namespaces; /* the array of namespaces */
148 };
149
150 /**
151 * xmlSchematronValidCtxt:
152 *
153 * A Schematrons validation context
154 */
155 struct _xmlSchematronValidCtxt {
156 int type;
157 int flags; /* an or of xmlSchematronValidOptions */
158
159 xmlDictPtr dict;
160 int nberrors;
161 int err;
162
163 xmlSchematronPtr schema;
164 xmlXPathContextPtr xctxt;
165
166 FILE *outputFile; /* if using XML_SCHEMATRON_OUT_FILE */
167 xmlBufferPtr outputBuffer; /* if using XML_SCHEMATRON_OUT_BUFFER */
168 xmlOutputWriteCallback iowrite; /* if using XML_SCHEMATRON_OUT_IO */
169 xmlOutputCloseCallback ioclose;
170 void *ioctx;
171
172 /* error reporting data */
173 void *userData; /* user specific data block */
174 xmlSchematronValidityErrorFunc error;/* the callback in case of errors */
175 xmlSchematronValidityWarningFunc warning;/* callback in case of warning */
176 xmlStructuredErrorFunc serror; /* the structured function */
177 };
178
179 struct _xmlSchematronParserCtxt {
180 int type;
181 const xmlChar *URL;
182 xmlDocPtr doc;
183 int preserve; /* Whether the doc should be freed */
184 const char *buffer;
185 int size;
186
187 xmlDictPtr dict; /* dictionnary for interned string names */
188
189 int nberrors;
190 int err;
191 xmlXPathContextPtr xctxt; /* the XPath context used for compilation */
192 xmlSchematronPtr schema;
193
194 int nbNamespaces; /* number of namespaces in the array */
195 int maxNamespaces; /* size of the array */
196 const xmlChar **namespaces; /* the array of namespaces */
197
198 int nbIncludes; /* number of includes in the array */
199 int maxIncludes; /* size of the array */
200 xmlNodePtr *includes; /* the array of includes */
201
202 /* error reporting data */
203 void *userData; /* user specific data block */
204 xmlSchematronValidityErrorFunc error;/* the callback in case of errors */
205 xmlSchematronValidityWarningFunc warning;/* callback in case of warning */
206 xmlStructuredErrorFunc serror; /* the structured function */
207 };
208
209 #define XML_STRON_CTXT_PARSER 1
210 #define XML_STRON_CTXT_VALIDATOR 2
211
212 /************************************************************************
213 * *
214 * Error reporting *
215 * *
216 ************************************************************************/
217
218 /**
219 * xmlSchematronPErrMemory:
220 * @node: a context node
221 * @extra: extra informations
222 *
223 * Handle an out of memory condition
224 */
225 static void
xmlSchematronPErrMemory(xmlSchematronParserCtxtPtr ctxt,const char * extra,xmlNodePtr node)226 xmlSchematronPErrMemory(xmlSchematronParserCtxtPtr ctxt,
227 const char *extra, xmlNodePtr node)
228 {
229 if (ctxt != NULL)
230 ctxt->nberrors++;
231 __xmlSimpleError(XML_FROM_SCHEMASP, XML_ERR_NO_MEMORY, node, NULL,
232 extra);
233 }
234
235 /**
236 * xmlSchematronPErr:
237 * @ctxt: the parsing context
238 * @node: the context node
239 * @error: the error code
240 * @msg: the error message
241 * @str1: extra data
242 * @str2: extra data
243 *
244 * Handle a parser error
245 */
246 static void
xmlSchematronPErr(xmlSchematronParserCtxtPtr ctxt,xmlNodePtr node,int error,const char * msg,const xmlChar * str1,const xmlChar * str2)247 xmlSchematronPErr(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr node, int error,
248 const char *msg, const xmlChar * str1, const xmlChar * str2)
249 {
250 xmlGenericErrorFunc channel = NULL;
251 xmlStructuredErrorFunc schannel = NULL;
252 void *data = NULL;
253
254 if (ctxt != NULL) {
255 ctxt->nberrors++;
256 channel = ctxt->error;
257 data = ctxt->userData;
258 schannel = ctxt->serror;
259 }
260 __xmlRaiseError(schannel, channel, data, ctxt, node, XML_FROM_SCHEMASP,
261 error, XML_ERR_ERROR, NULL, 0,
262 (const char *) str1, (const char *) str2, NULL, 0, 0,
263 msg, str1, str2);
264 }
265
266 /**
267 * xmlSchematronVTypeErrMemory:
268 * @node: a context node
269 * @extra: extra informations
270 *
271 * Handle an out of memory condition
272 */
273 static void
xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt,const char * extra,xmlNodePtr node)274 xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt,
275 const char *extra, xmlNodePtr node)
276 {
277 if (ctxt != NULL) {
278 ctxt->nberrors++;
279 ctxt->err = XML_SCHEMAV_INTERNAL;
280 }
281 __xmlSimpleError(XML_FROM_SCHEMASV, XML_ERR_NO_MEMORY, node, NULL,
282 extra);
283 }
284
285 /************************************************************************
286 * *
287 * Parsing and compilation of the Schematrontrons *
288 * *
289 ************************************************************************/
290
291 /**
292 * xmlSchematronAddTest:
293 * @ctxt: the schema parsing context
294 * @type: the type of test
295 * @rule: the parent rule
296 * @node: the node hosting the test
297 * @test: the associated test
298 * @report: the associated report string
299 *
300 * Add a test to a schematron
301 *
302 * Returns the new pointer or NULL in case of error
303 */
304 static xmlSchematronTestPtr
xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt,xmlSchematronTestType type,xmlSchematronRulePtr rule,xmlNodePtr node,xmlChar * test,xmlChar * report)305 xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt,
306 xmlSchematronTestType type,
307 xmlSchematronRulePtr rule,
308 xmlNodePtr node, xmlChar *test, xmlChar *report)
309 {
310 xmlSchematronTestPtr ret;
311 xmlXPathCompExprPtr comp;
312
313 if ((ctxt == NULL) || (rule == NULL) || (node == NULL) ||
314 (test == NULL))
315 return(NULL);
316
317 /*
318 * try first to compile the test expression
319 */
320 comp = xmlXPathCtxtCompile(ctxt->xctxt, test);
321 if (comp == NULL) {
322 xmlSchematronPErr(ctxt, node,
323 XML_SCHEMAP_NOROOT,
324 "Failed to compile test expression %s",
325 test, NULL);
326 return(NULL);
327 }
328
329 ret = (xmlSchematronTestPtr) xmlMalloc(sizeof(xmlSchematronTest));
330 if (ret == NULL) {
331 xmlSchematronPErrMemory(ctxt, "allocating schema test", node);
332 return (NULL);
333 }
334 memset(ret, 0, sizeof(xmlSchematronTest));
335 ret->type = type;
336 ret->node = node;
337 ret->test = test;
338 ret->comp = comp;
339 ret->report = report;
340 ret->next = NULL;
341 if (rule->tests == NULL) {
342 rule->tests = ret;
343 } else {
344 xmlSchematronTestPtr prev = rule->tests;
345
346 while (prev->next != NULL)
347 prev = prev->next;
348 prev->next = ret;
349 }
350 return (ret);
351 }
352
353 /**
354 * xmlSchematronFreeTests:
355 * @tests: a list of tests
356 *
357 * Free a list of tests.
358 */
359 static void
xmlSchematronFreeTests(xmlSchematronTestPtr tests)360 xmlSchematronFreeTests(xmlSchematronTestPtr tests) {
361 xmlSchematronTestPtr next;
362
363 while (tests != NULL) {
364 next = tests->next;
365 if (tests->test != NULL)
366 xmlFree(tests->test);
367 if (tests->comp != NULL)
368 xmlXPathFreeCompExpr(tests->comp);
369 if (tests->report != NULL)
370 xmlFree(tests->report);
371 xmlFree(tests);
372 tests = next;
373 }
374 }
375
376 /**
377 * xmlSchematronAddRule:
378 * @ctxt: the schema parsing context
379 * @schema: a schema structure
380 * @node: the node hosting the rule
381 * @context: the associated context string
382 * @report: the associated report string
383 *
384 * Add a rule to a schematron
385 *
386 * Returns the new pointer or NULL in case of error
387 */
388 static xmlSchematronRulePtr
xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt,xmlSchematronPtr schema,xmlSchematronPatternPtr pat,xmlNodePtr node,xmlChar * context,xmlChar * report)389 xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt, xmlSchematronPtr schema,
390 xmlSchematronPatternPtr pat, xmlNodePtr node,
391 xmlChar *context, xmlChar *report)
392 {
393 xmlSchematronRulePtr ret;
394 xmlPatternPtr pattern;
395
396 if ((ctxt == NULL) || (schema == NULL) || (node == NULL) ||
397 (context == NULL))
398 return(NULL);
399
400 /*
401 * Try first to compile the pattern
402 */
403 pattern = xmlPatterncompile(context, ctxt->dict, XML_PATTERN_XPATH,
404 ctxt->namespaces);
405 if (pattern == NULL) {
406 xmlSchematronPErr(ctxt, node,
407 XML_SCHEMAP_NOROOT,
408 "Failed to compile context expression %s",
409 context, NULL);
410 }
411
412 ret = (xmlSchematronRulePtr) xmlMalloc(sizeof(xmlSchematronRule));
413 if (ret == NULL) {
414 xmlSchematronPErrMemory(ctxt, "allocating schema rule", node);
415 return (NULL);
416 }
417 memset(ret, 0, sizeof(xmlSchematronRule));
418 ret->node = node;
419 ret->context = context;
420 ret->pattern = pattern;
421 ret->report = report;
422 ret->next = NULL;
423 if (schema->rules == NULL) {
424 schema->rules = ret;
425 } else {
426 xmlSchematronRulePtr prev = schema->rules;
427
428 while (prev->next != NULL)
429 prev = prev->next;
430 prev->next = ret;
431 }
432 ret->patnext = NULL;
433 if (pat->rules == NULL) {
434 pat->rules = ret;
435 } else {
436 xmlSchematronRulePtr prev = pat->rules;
437
438 while (prev->patnext != NULL)
439 prev = prev->patnext;
440 prev->patnext = ret;
441 }
442 return (ret);
443 }
444
445 /**
446 * xmlSchematronFreeRules:
447 * @rules: a list of rules
448 *
449 * Free a list of rules.
450 */
451 static void
xmlSchematronFreeRules(xmlSchematronRulePtr rules)452 xmlSchematronFreeRules(xmlSchematronRulePtr rules) {
453 xmlSchematronRulePtr next;
454
455 while (rules != NULL) {
456 next = rules->next;
457 if (rules->tests)
458 xmlSchematronFreeTests(rules->tests);
459 if (rules->context != NULL)
460 xmlFree(rules->context);
461 if (rules->pattern)
462 xmlFreePattern(rules->pattern);
463 if (rules->report != NULL)
464 xmlFree(rules->report);
465 xmlFree(rules);
466 rules = next;
467 }
468 }
469
470 /**
471 * xmlSchematronAddPattern:
472 * @ctxt: the schema parsing context
473 * @schema: a schema structure
474 * @node: the node hosting the pattern
475 * @id: the id or name of the pattern
476 *
477 * Add a pattern to a schematron
478 *
479 * Returns the new pointer or NULL in case of error
480 */
481 static xmlSchematronPatternPtr
xmlSchematronAddPattern(xmlSchematronParserCtxtPtr ctxt,xmlSchematronPtr schema,xmlNodePtr node,xmlChar * name)482 xmlSchematronAddPattern(xmlSchematronParserCtxtPtr ctxt,
483 xmlSchematronPtr schema, xmlNodePtr node, xmlChar *name)
484 {
485 xmlSchematronPatternPtr ret;
486
487 if ((ctxt == NULL) || (schema == NULL) || (node == NULL) || (name == NULL))
488 return(NULL);
489
490 ret = (xmlSchematronPatternPtr) xmlMalloc(sizeof(xmlSchematronPattern));
491 if (ret == NULL) {
492 xmlSchematronPErrMemory(ctxt, "allocating schema pattern", node);
493 return (NULL);
494 }
495 memset(ret, 0, sizeof(xmlSchematronPattern));
496 ret->name = name;
497 ret->next = NULL;
498 if (schema->patterns == NULL) {
499 schema->patterns = ret;
500 } else {
501 xmlSchematronPatternPtr prev = schema->patterns;
502
503 while (prev->next != NULL)
504 prev = prev->next;
505 prev->next = ret;
506 }
507 return (ret);
508 }
509
510 /**
511 * xmlSchematronFreePatterns:
512 * @patterns: a list of patterns
513 *
514 * Free a list of patterns.
515 */
516 static void
xmlSchematronFreePatterns(xmlSchematronPatternPtr patterns)517 xmlSchematronFreePatterns(xmlSchematronPatternPtr patterns) {
518 xmlSchematronPatternPtr next;
519
520 while (patterns != NULL) {
521 next = patterns->next;
522 if (patterns->name != NULL)
523 xmlFree(patterns->name);
524 xmlFree(patterns);
525 patterns = next;
526 }
527 }
528
529 /**
530 * xmlSchematronNewSchematron:
531 * @ctxt: a schema validation context
532 *
533 * Allocate a new Schematron structure.
534 *
535 * Returns the newly allocated structure or NULL in case or error
536 */
537 static xmlSchematronPtr
xmlSchematronNewSchematron(xmlSchematronParserCtxtPtr ctxt)538 xmlSchematronNewSchematron(xmlSchematronParserCtxtPtr ctxt)
539 {
540 xmlSchematronPtr ret;
541
542 ret = (xmlSchematronPtr) xmlMalloc(sizeof(xmlSchematron));
543 if (ret == NULL) {
544 xmlSchematronPErrMemory(ctxt, "allocating schema", NULL);
545 return (NULL);
546 }
547 memset(ret, 0, sizeof(xmlSchematron));
548 ret->dict = ctxt->dict;
549 xmlDictReference(ret->dict);
550
551 return (ret);
552 }
553
554 /**
555 * xmlSchematronFree:
556 * @schema: a schema structure
557 *
558 * Deallocate a Schematron structure.
559 */
560 void
xmlSchematronFree(xmlSchematronPtr schema)561 xmlSchematronFree(xmlSchematronPtr schema)
562 {
563 if (schema == NULL)
564 return;
565
566 if ((schema->doc != NULL) && (!(schema->preserve)))
567 xmlFreeDoc(schema->doc);
568
569 if (schema->namespaces != NULL)
570 xmlFree((char **) schema->namespaces);
571
572 xmlSchematronFreeRules(schema->rules);
573 xmlSchematronFreePatterns(schema->patterns);
574 xmlDictFree(schema->dict);
575 xmlFree(schema);
576 }
577
578 /**
579 * xmlSchematronNewParserCtxt:
580 * @URL: the location of the schema
581 *
582 * Create an XML Schematrons parse context for that file/resource expected
583 * to contain an XML Schematrons file.
584 *
585 * Returns the parser context or NULL in case of error
586 */
587 xmlSchematronParserCtxtPtr
xmlSchematronNewParserCtxt(const char * URL)588 xmlSchematronNewParserCtxt(const char *URL)
589 {
590 xmlSchematronParserCtxtPtr ret;
591
592 if (URL == NULL)
593 return (NULL);
594
595 ret =
596 (xmlSchematronParserCtxtPtr)
597 xmlMalloc(sizeof(xmlSchematronParserCtxt));
598 if (ret == NULL) {
599 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
600 NULL);
601 return (NULL);
602 }
603 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
604 ret->type = XML_STRON_CTXT_PARSER;
605 ret->dict = xmlDictCreate();
606 ret->URL = xmlDictLookup(ret->dict, (const xmlChar *) URL, -1);
607 ret->includes = NULL;
608 ret->xctxt = xmlXPathNewContext(NULL);
609 if (ret->xctxt == NULL) {
610 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
611 NULL);
612 xmlSchematronFreeParserCtxt(ret);
613 return (NULL);
614 }
615 ret->xctxt->flags = XML_XPATH_CHECKNS;
616 return (ret);
617 }
618
619 /**
620 * xmlSchematronNewMemParserCtxt:
621 * @buffer: a pointer to a char array containing the schemas
622 * @size: the size of the array
623 *
624 * Create an XML Schematrons parse context for that memory buffer expected
625 * to contain an XML Schematrons file.
626 *
627 * Returns the parser context or NULL in case of error
628 */
629 xmlSchematronParserCtxtPtr
xmlSchematronNewMemParserCtxt(const char * buffer,int size)630 xmlSchematronNewMemParserCtxt(const char *buffer, int size)
631 {
632 xmlSchematronParserCtxtPtr ret;
633
634 if ((buffer == NULL) || (size <= 0))
635 return (NULL);
636
637 ret =
638 (xmlSchematronParserCtxtPtr)
639 xmlMalloc(sizeof(xmlSchematronParserCtxt));
640 if (ret == NULL) {
641 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
642 NULL);
643 return (NULL);
644 }
645 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
646 ret->buffer = buffer;
647 ret->size = size;
648 ret->dict = xmlDictCreate();
649 ret->xctxt = xmlXPathNewContext(NULL);
650 if (ret->xctxt == NULL) {
651 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
652 NULL);
653 xmlSchematronFreeParserCtxt(ret);
654 return (NULL);
655 }
656 return (ret);
657 }
658
659 /**
660 * xmlSchematronNewDocParserCtxt:
661 * @doc: a preparsed document tree
662 *
663 * Create an XML Schematrons parse context for that document.
664 * NB. The document may be modified during the parsing process.
665 *
666 * Returns the parser context or NULL in case of error
667 */
668 xmlSchematronParserCtxtPtr
xmlSchematronNewDocParserCtxt(xmlDocPtr doc)669 xmlSchematronNewDocParserCtxt(xmlDocPtr doc)
670 {
671 xmlSchematronParserCtxtPtr ret;
672
673 if (doc == NULL)
674 return (NULL);
675
676 ret =
677 (xmlSchematronParserCtxtPtr)
678 xmlMalloc(sizeof(xmlSchematronParserCtxt));
679 if (ret == NULL) {
680 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
681 NULL);
682 return (NULL);
683 }
684 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
685 ret->doc = doc;
686 ret->dict = xmlDictCreate();
687 /* The application has responsibility for the document */
688 ret->preserve = 1;
689 ret->xctxt = xmlXPathNewContext(doc);
690 if (ret->xctxt == NULL) {
691 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
692 NULL);
693 xmlSchematronFreeParserCtxt(ret);
694 return (NULL);
695 }
696
697 return (ret);
698 }
699
700 /**
701 * xmlSchematronFreeParserCtxt:
702 * @ctxt: the schema parser context
703 *
704 * Free the resources associated to the schema parser context
705 */
706 void
xmlSchematronFreeParserCtxt(xmlSchematronParserCtxtPtr ctxt)707 xmlSchematronFreeParserCtxt(xmlSchematronParserCtxtPtr ctxt)
708 {
709 if (ctxt == NULL)
710 return;
711 if (ctxt->doc != NULL && !ctxt->preserve)
712 xmlFreeDoc(ctxt->doc);
713 if (ctxt->xctxt != NULL) {
714 xmlXPathFreeContext(ctxt->xctxt);
715 }
716 if (ctxt->namespaces != NULL)
717 xmlFree((char **) ctxt->namespaces);
718 xmlDictFree(ctxt->dict);
719 xmlFree(ctxt);
720 }
721
722 #if 0
723 /**
724 * xmlSchematronPushInclude:
725 * @ctxt: the schema parser context
726 * @doc: the included document
727 * @cur: the current include node
728 *
729 * Add an included document
730 */
731 static void
732 xmlSchematronPushInclude(xmlSchematronParserCtxtPtr ctxt,
733 xmlDocPtr doc, xmlNodePtr cur)
734 {
735 if (ctxt->includes == NULL) {
736 ctxt->maxIncludes = 10;
737 ctxt->includes = (xmlNodePtr *)
738 xmlMalloc(ctxt->maxIncludes * 2 * sizeof(xmlNodePtr));
739 if (ctxt->includes == NULL) {
740 xmlSchematronPErrMemory(NULL, "allocating parser includes",
741 NULL);
742 return;
743 }
744 ctxt->nbIncludes = 0;
745 } else if (ctxt->nbIncludes + 2 >= ctxt->maxIncludes) {
746 xmlNodePtr *tmp;
747
748 tmp = (xmlNodePtr *)
749 xmlRealloc(ctxt->includes, ctxt->maxIncludes * 4 *
750 sizeof(xmlNodePtr));
751 if (tmp == NULL) {
752 xmlSchematronPErrMemory(NULL, "allocating parser includes",
753 NULL);
754 return;
755 }
756 ctxt->includes = tmp;
757 ctxt->maxIncludes *= 2;
758 }
759 ctxt->includes[2 * ctxt->nbIncludes] = cur;
760 ctxt->includes[2 * ctxt->nbIncludes + 1] = (xmlNodePtr) doc;
761 ctxt->nbIncludes++;
762 }
763
764 /**
765 * xmlSchematronPopInclude:
766 * @ctxt: the schema parser context
767 *
768 * Pop an include level. The included document is being freed
769 *
770 * Returns the node immediately following the include or NULL if the
771 * include list was empty.
772 */
773 static xmlNodePtr
774 xmlSchematronPopInclude(xmlSchematronParserCtxtPtr ctxt)
775 {
776 xmlDocPtr doc;
777 xmlNodePtr ret;
778
779 if (ctxt->nbIncludes <= 0)
780 return(NULL);
781 ctxt->nbIncludes--;
782 doc = (xmlDocPtr) ctxt->includes[2 * ctxt->nbIncludes + 1];
783 ret = ctxt->includes[2 * ctxt->nbIncludes];
784 xmlFreeDoc(doc);
785 if (ret != NULL)
786 ret = ret->next;
787 if (ret == NULL)
788 return(xmlSchematronPopInclude(ctxt));
789 return(ret);
790 }
791 #endif
792
793 /**
794 * xmlSchematronAddNamespace:
795 * @ctxt: the schema parser context
796 * @prefix: the namespace prefix
797 * @ns: the namespace name
798 *
799 * Add a namespace definition in the context
800 */
801 static void
xmlSchematronAddNamespace(xmlSchematronParserCtxtPtr ctxt,const xmlChar * prefix,const xmlChar * ns)802 xmlSchematronAddNamespace(xmlSchematronParserCtxtPtr ctxt,
803 const xmlChar *prefix, const xmlChar *ns)
804 {
805 if (ctxt->namespaces == NULL) {
806 ctxt->maxNamespaces = 10;
807 ctxt->namespaces = (const xmlChar **)
808 xmlMalloc(ctxt->maxNamespaces * 2 * sizeof(const xmlChar *));
809 if (ctxt->namespaces == NULL) {
810 xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
811 NULL);
812 return;
813 }
814 ctxt->nbNamespaces = 0;
815 } else if (ctxt->nbNamespaces + 2 >= ctxt->maxNamespaces) {
816 const xmlChar **tmp;
817
818 tmp = (const xmlChar **)
819 xmlRealloc((xmlChar **) ctxt->namespaces, ctxt->maxNamespaces * 4 *
820 sizeof(const xmlChar *));
821 if (tmp == NULL) {
822 xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
823 NULL);
824 return;
825 }
826 ctxt->namespaces = tmp;
827 ctxt->maxNamespaces *= 2;
828 }
829 ctxt->namespaces[2 * ctxt->nbNamespaces] =
830 xmlDictLookup(ctxt->dict, ns, -1);
831 ctxt->namespaces[2 * ctxt->nbNamespaces + 1] =
832 xmlDictLookup(ctxt->dict, prefix, -1);
833 ctxt->nbNamespaces++;
834 ctxt->namespaces[2 * ctxt->nbNamespaces] = NULL;
835 ctxt->namespaces[2 * ctxt->nbNamespaces + 1] = NULL;
836
837 }
838
839 /**
840 * xmlSchematronParseRule:
841 * @ctxt: a schema validation context
842 * @rule: the rule node
843 *
844 * parse a rule element
845 */
846 static void
xmlSchematronParseRule(xmlSchematronParserCtxtPtr ctxt,xmlSchematronPatternPtr pattern,xmlNodePtr rule)847 xmlSchematronParseRule(xmlSchematronParserCtxtPtr ctxt,
848 xmlSchematronPatternPtr pattern,
849 xmlNodePtr rule)
850 {
851 xmlNodePtr cur;
852 int nbChecks = 0;
853 xmlChar *test;
854 xmlChar *context;
855 xmlChar *report;
856 xmlSchematronRulePtr ruleptr;
857 xmlSchematronTestPtr testptr;
858
859 if ((ctxt == NULL) || (rule == NULL)) return;
860
861 context = xmlGetNoNsProp(rule, BAD_CAST "context");
862 if (context == NULL) {
863 xmlSchematronPErr(ctxt, rule,
864 XML_SCHEMAP_NOROOT,
865 "rule has no context attribute",
866 NULL, NULL);
867 return;
868 } else if (context[0] == 0) {
869 xmlSchematronPErr(ctxt, rule,
870 XML_SCHEMAP_NOROOT,
871 "rule has an empty context attribute",
872 NULL, NULL);
873 xmlFree(context);
874 return;
875 } else {
876 ruleptr = xmlSchematronAddRule(ctxt, ctxt->schema, pattern,
877 rule, context, NULL);
878 if (ruleptr == NULL) {
879 xmlFree(context);
880 return;
881 }
882 }
883
884 cur = rule->children;
885 NEXT_SCHEMATRON(cur);
886 while (cur != NULL) {
887 if (IS_SCHEMATRON(cur, "assert")) {
888 nbChecks++;
889 test = xmlGetNoNsProp(cur, BAD_CAST "test");
890 if (test == NULL) {
891 xmlSchematronPErr(ctxt, cur,
892 XML_SCHEMAP_NOROOT,
893 "assert has no test attribute",
894 NULL, NULL);
895 } else if (test[0] == 0) {
896 xmlSchematronPErr(ctxt, cur,
897 XML_SCHEMAP_NOROOT,
898 "assert has an empty test attribute",
899 NULL, NULL);
900 xmlFree(test);
901 } else {
902 /* TODO will need dynamic processing instead */
903 report = xmlNodeGetContent(cur);
904
905 testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_ASSERT,
906 ruleptr, cur, test, report);
907 if (testptr == NULL)
908 xmlFree(test);
909 }
910 } else if (IS_SCHEMATRON(cur, "report")) {
911 nbChecks++;
912 test = xmlGetNoNsProp(cur, BAD_CAST "test");
913 if (test == NULL) {
914 xmlSchematronPErr(ctxt, cur,
915 XML_SCHEMAP_NOROOT,
916 "assert has no test attribute",
917 NULL, NULL);
918 } else if (test[0] == 0) {
919 xmlSchematronPErr(ctxt, cur,
920 XML_SCHEMAP_NOROOT,
921 "assert has an empty test attribute",
922 NULL, NULL);
923 xmlFree(test);
924 } else {
925 /* TODO will need dynamic processing instead */
926 report = xmlNodeGetContent(cur);
927
928 testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_REPORT,
929 ruleptr, cur, test, report);
930 if (testptr == NULL)
931 xmlFree(test);
932 }
933 } else {
934 xmlSchematronPErr(ctxt, cur,
935 XML_SCHEMAP_NOROOT,
936 "Expecting an assert or a report element instead of %s",
937 cur->name, NULL);
938 }
939 cur = cur->next;
940 NEXT_SCHEMATRON(cur);
941 }
942 if (nbChecks == 0) {
943 xmlSchematronPErr(ctxt, rule,
944 XML_SCHEMAP_NOROOT,
945 "rule has no assert nor report element", NULL, NULL);
946 }
947 }
948
949 /**
950 * xmlSchematronParsePattern:
951 * @ctxt: a schema validation context
952 * @pat: the pattern node
953 *
954 * parse a pattern element
955 */
956 static void
xmlSchematronParsePattern(xmlSchematronParserCtxtPtr ctxt,xmlNodePtr pat)957 xmlSchematronParsePattern(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr pat)
958 {
959 xmlNodePtr cur;
960 xmlSchematronPatternPtr pattern;
961 int nbRules = 0;
962 xmlChar *id;
963
964 if ((ctxt == NULL) || (pat == NULL)) return;
965
966 id = xmlGetNoNsProp(pat, BAD_CAST "id");
967 if (id == NULL) {
968 id = xmlGetNoNsProp(pat, BAD_CAST "name");
969 }
970 pattern = xmlSchematronAddPattern(ctxt, ctxt->schema, pat, id);
971 if (pattern == NULL) {
972 if (id != NULL)
973 xmlFree(id);
974 return;
975 }
976 cur = pat->children;
977 NEXT_SCHEMATRON(cur);
978 while (cur != NULL) {
979 if (IS_SCHEMATRON(cur, "rule")) {
980 xmlSchematronParseRule(ctxt, pattern, cur);
981 nbRules++;
982 } else {
983 xmlSchematronPErr(ctxt, cur,
984 XML_SCHEMAP_NOROOT,
985 "Expecting a rule element instead of %s", cur->name, NULL);
986 }
987 cur = cur->next;
988 NEXT_SCHEMATRON(cur);
989 }
990 if (nbRules == 0) {
991 xmlSchematronPErr(ctxt, pat,
992 XML_SCHEMAP_NOROOT,
993 "Pattern has no rule element", NULL, NULL);
994 }
995 }
996
997 #if 0
998 /**
999 * xmlSchematronLoadInclude:
1000 * @ctxt: a schema validation context
1001 * @cur: the include element
1002 *
1003 * Load the include document, Push the current pointer
1004 *
1005 * Returns the updated node pointer
1006 */
1007 static xmlNodePtr
1008 xmlSchematronLoadInclude(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr cur)
1009 {
1010 xmlNodePtr ret = NULL;
1011 xmlDocPtr doc = NULL;
1012 xmlChar *href = NULL;
1013 xmlChar *base = NULL;
1014 xmlChar *URI = NULL;
1015
1016 if ((ctxt == NULL) || (cur == NULL))
1017 return(NULL);
1018
1019 href = xmlGetNoNsProp(cur, BAD_CAST "href");
1020 if (href == NULL) {
1021 xmlSchematronPErr(ctxt, cur,
1022 XML_SCHEMAP_NOROOT,
1023 "Include has no href attribute", NULL, NULL);
1024 return(cur->next);
1025 }
1026
1027 /* do the URI base composition, load and find the root */
1028 base = xmlNodeGetBase(cur->doc, cur);
1029 URI = xmlBuildURI(href, base);
1030 doc = xmlReadFile((const char *) URI, NULL, SCHEMATRON_PARSE_OPTIONS);
1031 if (doc == NULL) {
1032 xmlSchematronPErr(ctxt, cur,
1033 XML_SCHEMAP_FAILED_LOAD,
1034 "could not load include '%s'.\n",
1035 URI, NULL);
1036 goto done;
1037 }
1038 ret = xmlDocGetRootElement(doc);
1039 if (ret == NULL) {
1040 xmlSchematronPErr(ctxt, cur,
1041 XML_SCHEMAP_FAILED_LOAD,
1042 "could not find root from include '%s'.\n",
1043 URI, NULL);
1044 goto done;
1045 }
1046
1047 /* Success, push the include for rollback on exit */
1048 xmlSchematronPushInclude(ctxt, doc, cur);
1049
1050 done:
1051 if (ret == NULL) {
1052 if (doc != NULL)
1053 xmlFreeDoc(doc);
1054 }
1055 xmlFree(href);
1056 if (base != NULL)
1057 xmlFree(base);
1058 if (URI != NULL)
1059 xmlFree(URI);
1060 return(ret);
1061 }
1062 #endif
1063
1064 /**
1065 * xmlSchematronParse:
1066 * @ctxt: a schema validation context
1067 *
1068 * parse a schema definition resource and build an internal
1069 * XML Shema struture which can be used to validate instances.
1070 *
1071 * Returns the internal XML Schematron structure built from the resource or
1072 * NULL in case of error
1073 */
1074 xmlSchematronPtr
xmlSchematronParse(xmlSchematronParserCtxtPtr ctxt)1075 xmlSchematronParse(xmlSchematronParserCtxtPtr ctxt)
1076 {
1077 xmlSchematronPtr ret = NULL;
1078 xmlDocPtr doc;
1079 xmlNodePtr root, cur;
1080 int preserve = 0;
1081
1082 if (ctxt == NULL)
1083 return (NULL);
1084
1085 ctxt->nberrors = 0;
1086
1087 /*
1088 * First step is to parse the input document into an DOM/Infoset
1089 */
1090 if (ctxt->URL != NULL) {
1091 doc = xmlReadFile((const char *) ctxt->URL, NULL,
1092 SCHEMATRON_PARSE_OPTIONS);
1093 if (doc == NULL) {
1094 xmlSchematronPErr(ctxt, NULL,
1095 XML_SCHEMAP_FAILED_LOAD,
1096 "xmlSchematronParse: could not load '%s'.\n",
1097 ctxt->URL, NULL);
1098 return (NULL);
1099 }
1100 ctxt->preserve = 0;
1101 } else if (ctxt->buffer != NULL) {
1102 doc = xmlReadMemory(ctxt->buffer, ctxt->size, NULL, NULL,
1103 SCHEMATRON_PARSE_OPTIONS);
1104 if (doc == NULL) {
1105 xmlSchematronPErr(ctxt, NULL,
1106 XML_SCHEMAP_FAILED_PARSE,
1107 "xmlSchematronParse: could not parse.\n",
1108 NULL, NULL);
1109 return (NULL);
1110 }
1111 doc->URL = xmlStrdup(BAD_CAST "in_memory_buffer");
1112 ctxt->URL = xmlDictLookup(ctxt->dict, BAD_CAST "in_memory_buffer", -1);
1113 ctxt->preserve = 0;
1114 } else if (ctxt->doc != NULL) {
1115 doc = ctxt->doc;
1116 preserve = 1;
1117 ctxt->preserve = 1;
1118 } else {
1119 xmlSchematronPErr(ctxt, NULL,
1120 XML_SCHEMAP_NOTHING_TO_PARSE,
1121 "xmlSchematronParse: could not parse.\n",
1122 NULL, NULL);
1123 return (NULL);
1124 }
1125
1126 /*
1127 * Then extract the root and Schematron parse it
1128 */
1129 root = xmlDocGetRootElement(doc);
1130 if (root == NULL) {
1131 xmlSchematronPErr(ctxt, (xmlNodePtr) doc,
1132 XML_SCHEMAP_NOROOT,
1133 "The schema has no document element.\n", NULL, NULL);
1134 if (!preserve) {
1135 xmlFreeDoc(doc);
1136 }
1137 return (NULL);
1138 }
1139
1140 if (!IS_SCHEMATRON(root, "schema")) {
1141 xmlSchematronPErr(ctxt, root,
1142 XML_SCHEMAP_NOROOT,
1143 "The XML document '%s' is not a XML schematron document",
1144 ctxt->URL, NULL);
1145 goto exit;
1146 }
1147 ret = xmlSchematronNewSchematron(ctxt);
1148 if (ret == NULL)
1149 goto exit;
1150 ctxt->schema = ret;
1151
1152 /*
1153 * scan the schema elements
1154 */
1155 cur = root->children;
1156 NEXT_SCHEMATRON(cur);
1157 if (IS_SCHEMATRON(cur, "title")) {
1158 xmlChar *title = xmlNodeGetContent(cur);
1159 if (title != NULL) {
1160 ret->title = xmlDictLookup(ret->dict, title, -1);
1161 xmlFree(title);
1162 }
1163 cur = cur->next;
1164 NEXT_SCHEMATRON(cur);
1165 }
1166 while (IS_SCHEMATRON(cur, "ns")) {
1167 xmlChar *prefix = xmlGetNoNsProp(cur, BAD_CAST "prefix");
1168 xmlChar *uri = xmlGetNoNsProp(cur, BAD_CAST "uri");
1169 if ((uri == NULL) || (uri[0] == 0)) {
1170 xmlSchematronPErr(ctxt, cur,
1171 XML_SCHEMAP_NOROOT,
1172 "ns element has no uri", NULL, NULL);
1173 }
1174 if ((prefix == NULL) || (prefix[0] == 0)) {
1175 xmlSchematronPErr(ctxt, cur,
1176 XML_SCHEMAP_NOROOT,
1177 "ns element has no prefix", NULL, NULL);
1178 }
1179 if ((prefix) && (uri)) {
1180 xmlXPathRegisterNs(ctxt->xctxt, prefix, uri);
1181 xmlSchematronAddNamespace(ctxt, prefix, uri);
1182 ret->nbNs++;
1183 }
1184 if (uri)
1185 xmlFree(uri);
1186 if (prefix)
1187 xmlFree(prefix);
1188 cur = cur->next;
1189 NEXT_SCHEMATRON(cur);
1190 }
1191 while (cur != NULL) {
1192 if (IS_SCHEMATRON(cur, "pattern")) {
1193 xmlSchematronParsePattern(ctxt, cur);
1194 ret->nbPattern++;
1195 } else {
1196 xmlSchematronPErr(ctxt, cur,
1197 XML_SCHEMAP_NOROOT,
1198 "Expecting a pattern element instead of %s", cur->name, NULL);
1199 }
1200 cur = cur->next;
1201 NEXT_SCHEMATRON(cur);
1202 }
1203 if (ret->nbPattern == 0) {
1204 xmlSchematronPErr(ctxt, root,
1205 XML_SCHEMAP_NOROOT,
1206 "The schematron document '%s' has no pattern",
1207 ctxt->URL, NULL);
1208 goto exit;
1209 }
1210 /* the original document must be kept for reporting */
1211 ret->doc = doc;
1212 if (preserve) {
1213 ret->preserve = 1;
1214 }
1215 preserve = 1;
1216
1217 exit:
1218 if (!preserve) {
1219 xmlFreeDoc(doc);
1220 }
1221 if (ret != NULL) {
1222 if (ctxt->nberrors != 0) {
1223 xmlSchematronFree(ret);
1224 ret = NULL;
1225 } else {
1226 ret->namespaces = ctxt->namespaces;
1227 ret->nbNamespaces = ctxt->nbNamespaces;
1228 ctxt->namespaces = NULL;
1229 }
1230 }
1231 return (ret);
1232 }
1233
1234 /************************************************************************
1235 * *
1236 * Schematrontron Reports handler *
1237 * *
1238 ************************************************************************/
1239
1240 static xmlNodePtr
xmlSchematronGetNode(xmlSchematronValidCtxtPtr ctxt,xmlNodePtr cur,const xmlChar * xpath)1241 xmlSchematronGetNode(xmlSchematronValidCtxtPtr ctxt,
1242 xmlNodePtr cur, const xmlChar *xpath) {
1243 xmlNodePtr node = NULL;
1244 xmlXPathObjectPtr ret;
1245
1246 if ((ctxt == NULL) || (cur == NULL) || (xpath == NULL))
1247 return(NULL);
1248
1249 ctxt->xctxt->doc = cur->doc;
1250 ctxt->xctxt->node = cur;
1251 ret = xmlXPathEval(xpath, ctxt->xctxt);
1252 if (ret == NULL)
1253 return(NULL);
1254
1255 if ((ret->type == XPATH_NODESET) &&
1256 (ret->nodesetval != NULL) && (ret->nodesetval->nodeNr > 0))
1257 node = ret->nodesetval->nodeTab[0];
1258
1259 xmlXPathFreeObject(ret);
1260 return(node);
1261 }
1262
1263 /**
1264 * xmlSchematronReportOutput:
1265 * @ctxt: the validation context
1266 * @cur: the current node tested
1267 * @msg: the message output
1268 *
1269 * Output part of the report to whatever channel the user selected
1270 */
1271 static void
xmlSchematronReportOutput(xmlSchematronValidCtxtPtr ctxt ATTRIBUTE_UNUSED,xmlNodePtr cur ATTRIBUTE_UNUSED,const char * msg)1272 xmlSchematronReportOutput(xmlSchematronValidCtxtPtr ctxt ATTRIBUTE_UNUSED,
1273 xmlNodePtr cur ATTRIBUTE_UNUSED,
1274 const char *msg) {
1275 /* TODO */
1276 fprintf(stderr, "%s", msg);
1277 }
1278
1279 /**
1280 * xmlSchematronFormatReport:
1281 * @ctxt: the validation context
1282 * @test: the test node
1283 * @cur: the current node tested
1284 *
1285 * Build the string being reported to the user.
1286 *
1287 * Returns a report string or NULL in case of error. The string needs
1288 * to be deallocated by teh caller
1289 */
1290 static xmlChar *
xmlSchematronFormatReport(xmlSchematronValidCtxtPtr ctxt,xmlNodePtr test,xmlNodePtr cur)1291 xmlSchematronFormatReport(xmlSchematronValidCtxtPtr ctxt,
1292 xmlNodePtr test, xmlNodePtr cur) {
1293 xmlChar *ret = NULL;
1294 xmlNodePtr child, node;
1295
1296 if ((test == NULL) || (cur == NULL))
1297 return(ret);
1298
1299 child = test->children;
1300 while (child != NULL) {
1301 if ((child->type == XML_TEXT_NODE) ||
1302 (child->type == XML_CDATA_SECTION_NODE))
1303 ret = xmlStrcat(ret, child->content);
1304 else if (IS_SCHEMATRON(child, "name")) {
1305 xmlChar *path;
1306
1307 path = xmlGetNoNsProp(child, BAD_CAST "path");
1308
1309 node = cur;
1310 if (path != NULL) {
1311 node = xmlSchematronGetNode(ctxt, cur, path);
1312 if (node == NULL)
1313 node = cur;
1314 xmlFree(path);
1315 }
1316
1317 if ((node->ns == NULL) || (node->ns->prefix == NULL))
1318 ret = xmlStrcat(ret, node->name);
1319 else {
1320 ret = xmlStrcat(ret, node->ns->prefix);
1321 ret = xmlStrcat(ret, BAD_CAST ":");
1322 ret = xmlStrcat(ret, node->name);
1323 }
1324 } else {
1325 child = child->next;
1326 continue;
1327 }
1328
1329 /*
1330 * remove superfluous \n
1331 */
1332 if (ret != NULL) {
1333 int len = xmlStrlen(ret);
1334 xmlChar c;
1335
1336 if (len > 0) {
1337 c = ret[len - 1];
1338 if ((c == ' ') || (c == '\n') || (c == '\r') || (c == '\t')) {
1339 while ((c == ' ') || (c == '\n') ||
1340 (c == '\r') || (c == '\t')) {
1341 len--;
1342 if (len == 0)
1343 break;
1344 c = ret[len - 1];
1345 }
1346 ret[len] = ' ';
1347 ret[len + 1] = 0;
1348 }
1349 }
1350 }
1351
1352 child = child->next;
1353 }
1354 return(ret);
1355 }
1356
1357 /**
1358 * xmlSchematronReportSuccess:
1359 * @ctxt: the validation context
1360 * @test: the compiled test
1361 * @cur: the current node tested
1362 * @success: boolean value for the result
1363 *
1364 * called from the validation engine when an assert or report test have
1365 * been done.
1366 */
1367 static void
xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt,xmlSchematronTestPtr test,xmlNodePtr cur,xmlSchematronPatternPtr pattern,int success)1368 xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt,
1369 xmlSchematronTestPtr test, xmlNodePtr cur, xmlSchematronPatternPtr pattern, int success) {
1370 if ((ctxt == NULL) || (cur == NULL) || (test == NULL))
1371 return;
1372 /* if quiet and not SVRL report only failures */
1373 if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) &&
1374 ((ctxt->flags & XML_SCHEMATRON_OUT_XML) == 0) &&
1375 (test->type == XML_SCHEMATRON_REPORT))
1376 return;
1377 if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
1378 TODO
1379 } else {
1380 xmlChar *path;
1381 char msg[1000];
1382 long line;
1383 const xmlChar *report = NULL;
1384
1385 if (((test->type == XML_SCHEMATRON_REPORT) & (!success)) ||
1386 ((test->type == XML_SCHEMATRON_ASSERT) & (success)))
1387 return;
1388 line = xmlGetLineNo(cur);
1389 path = xmlGetNodePath(cur);
1390 if (path == NULL)
1391 path = (xmlChar *) cur->name;
1392 #if 0
1393 if ((test->report != NULL) && (test->report[0] != 0))
1394 report = test->report;
1395 #endif
1396 if (test->node != NULL)
1397 report = xmlSchematronFormatReport(ctxt, test->node, cur);
1398 if (report == NULL) {
1399 if (test->type == XML_SCHEMATRON_ASSERT) {
1400 report = xmlStrdup((const xmlChar *) "node failed assert");
1401 } else {
1402 report = xmlStrdup((const xmlChar *) "node failed report");
1403 }
1404 }
1405 snprintf(msg, 999, "%s line %ld: %s\n", (const char *) path,
1406 line, (const char *) report);
1407
1408 if (ctxt->flags & XML_SCHEMATRON_OUT_ERROR) {
1409 xmlStructuredErrorFunc schannel = NULL;
1410 xmlGenericErrorFunc channel = NULL;
1411 void *data = NULL;
1412
1413 if (ctxt != NULL) {
1414 if (ctxt->serror != NULL)
1415 schannel = ctxt->serror;
1416 else
1417 channel = ctxt->error;
1418 data = ctxt->userData;
1419 }
1420
1421 __xmlRaiseError(schannel, channel, data,
1422 NULL, cur, XML_FROM_SCHEMATRONV,
1423 (test->type == XML_SCHEMATRON_ASSERT)?XML_SCHEMATRONV_ASSERT:XML_SCHEMATRONV_REPORT,
1424 XML_ERR_ERROR, NULL, line,
1425 (pattern == NULL)?NULL:((const char *) pattern->name),
1426 (const char *) path,
1427 (const char *) report, 0, 0,
1428 "%s", msg);
1429 } else {
1430 xmlSchematronReportOutput(ctxt, cur, &msg[0]);
1431 }
1432
1433 xmlFree((char *) report);
1434
1435 if ((path != NULL) && (path != (xmlChar *) cur->name))
1436 xmlFree(path);
1437 }
1438 }
1439
1440 /**
1441 * xmlSchematronReportPattern:
1442 * @ctxt: the validation context
1443 * @pattern: the current pattern
1444 *
1445 * called from the validation engine when starting to check a pattern
1446 */
1447 static void
xmlSchematronReportPattern(xmlSchematronValidCtxtPtr ctxt,xmlSchematronPatternPtr pattern)1448 xmlSchematronReportPattern(xmlSchematronValidCtxtPtr ctxt,
1449 xmlSchematronPatternPtr pattern) {
1450 if ((ctxt == NULL) || (pattern == NULL))
1451 return;
1452 if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) || (ctxt->flags & XML_SCHEMATRON_OUT_ERROR)) /* Error gives pattern name as part of error */
1453 return;
1454 if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
1455 TODO
1456 } else {
1457 char msg[1000];
1458
1459 if (pattern->name == NULL)
1460 return;
1461 snprintf(msg, 999, "Pattern: %s\n", (const char *) pattern->name);
1462 xmlSchematronReportOutput(ctxt, NULL, &msg[0]);
1463 }
1464 }
1465
1466
1467 /************************************************************************
1468 * *
1469 * Validation against a Schematrontron *
1470 * *
1471 ************************************************************************/
1472
1473 /**
1474 * xmlSchematronSetValidStructuredErrors:
1475 * @ctxt: a Schematron validation context
1476 * @serror: the structured error function
1477 * @ctx: the functions context
1478 *
1479 * Set the structured error callback
1480 */
1481 void
xmlSchematronSetValidStructuredErrors(xmlSchematronValidCtxtPtr ctxt,xmlStructuredErrorFunc serror,void * ctx)1482 xmlSchematronSetValidStructuredErrors(xmlSchematronValidCtxtPtr ctxt,
1483 xmlStructuredErrorFunc serror, void *ctx)
1484 {
1485 if (ctxt == NULL)
1486 return;
1487 ctxt->serror = serror;
1488 ctxt->error = NULL;
1489 ctxt->warning = NULL;
1490 ctxt->userData = ctx;
1491 }
1492
1493 /**
1494 * xmlSchematronNewValidCtxt:
1495 * @schema: a precompiled XML Schematrons
1496 * @options: a set of xmlSchematronValidOptions
1497 *
1498 * Create an XML Schematrons validation context based on the given schema.
1499 *
1500 * Returns the validation context or NULL in case of error
1501 */
1502 xmlSchematronValidCtxtPtr
xmlSchematronNewValidCtxt(xmlSchematronPtr schema,int options)1503 xmlSchematronNewValidCtxt(xmlSchematronPtr schema, int options)
1504 {
1505 int i;
1506 xmlSchematronValidCtxtPtr ret;
1507
1508 ret = (xmlSchematronValidCtxtPtr) xmlMalloc(sizeof(xmlSchematronValidCtxt));
1509 if (ret == NULL) {
1510 xmlSchematronVErrMemory(NULL, "allocating validation context",
1511 NULL);
1512 return (NULL);
1513 }
1514 memset(ret, 0, sizeof(xmlSchematronValidCtxt));
1515 ret->type = XML_STRON_CTXT_VALIDATOR;
1516 ret->schema = schema;
1517 ret->xctxt = xmlXPathNewContext(NULL);
1518 ret->flags = options;
1519 if (ret->xctxt == NULL) {
1520 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
1521 NULL);
1522 xmlSchematronFreeValidCtxt(ret);
1523 return (NULL);
1524 }
1525 for (i = 0;i < schema->nbNamespaces;i++) {
1526 if ((schema->namespaces[2 * i] == NULL) ||
1527 (schema->namespaces[2 * i + 1] == NULL))
1528 break;
1529 xmlXPathRegisterNs(ret->xctxt, schema->namespaces[2 * i + 1],
1530 schema->namespaces[2 * i]);
1531 }
1532 return (ret);
1533 }
1534
1535 /**
1536 * xmlSchematronFreeValidCtxt:
1537 * @ctxt: the schema validation context
1538 *
1539 * Free the resources associated to the schema validation context
1540 */
1541 void
xmlSchematronFreeValidCtxt(xmlSchematronValidCtxtPtr ctxt)1542 xmlSchematronFreeValidCtxt(xmlSchematronValidCtxtPtr ctxt)
1543 {
1544 if (ctxt == NULL)
1545 return;
1546 if (ctxt->xctxt != NULL)
1547 xmlXPathFreeContext(ctxt->xctxt);
1548 if (ctxt->dict != NULL)
1549 xmlDictFree(ctxt->dict);
1550 xmlFree(ctxt);
1551 }
1552
1553 static xmlNodePtr
xmlSchematronNextNode(xmlNodePtr cur)1554 xmlSchematronNextNode(xmlNodePtr cur) {
1555 if (cur->children != NULL) {
1556 /*
1557 * Do not descend on entities declarations
1558 */
1559 if (cur->children->type != XML_ENTITY_DECL) {
1560 cur = cur->children;
1561 /*
1562 * Skip DTDs
1563 */
1564 if (cur->type != XML_DTD_NODE)
1565 return(cur);
1566 }
1567 }
1568
1569 while (cur->next != NULL) {
1570 cur = cur->next;
1571 if ((cur->type != XML_ENTITY_DECL) &&
1572 (cur->type != XML_DTD_NODE))
1573 return(cur);
1574 }
1575
1576 do {
1577 cur = cur->parent;
1578 if (cur == NULL) break;
1579 if (cur->type == XML_DOCUMENT_NODE) return(NULL);
1580 if (cur->next != NULL) {
1581 cur = cur->next;
1582 return(cur);
1583 }
1584 } while (cur != NULL);
1585 return(cur);
1586 }
1587
1588 /**
1589 * xmlSchematronRunTest:
1590 * @ctxt: the schema validation context
1591 * @test: the current test
1592 * @instance: the document instace tree
1593 * @cur: the current node in the instance
1594 *
1595 * Validate a rule against a tree instance at a given position
1596 *
1597 * Returns 1 in case of success, 0 if error and -1 in case of internal error
1598 */
1599 static int
xmlSchematronRunTest(xmlSchematronValidCtxtPtr ctxt,xmlSchematronTestPtr test,xmlDocPtr instance,xmlNodePtr cur,xmlSchematronPatternPtr pattern)1600 xmlSchematronRunTest(xmlSchematronValidCtxtPtr ctxt,
1601 xmlSchematronTestPtr test, xmlDocPtr instance, xmlNodePtr cur, xmlSchematronPatternPtr pattern)
1602 {
1603 xmlXPathObjectPtr ret;
1604 int failed;
1605
1606 failed = 0;
1607 ctxt->xctxt->doc = instance;
1608 ctxt->xctxt->node = cur;
1609 ret = xmlXPathCompiledEval(test->comp, ctxt->xctxt);
1610 if (ret == NULL) {
1611 failed = 1;
1612 } else {
1613 switch (ret->type) {
1614 case XPATH_XSLT_TREE:
1615 case XPATH_NODESET:
1616 if ((ret->nodesetval == NULL) ||
1617 (ret->nodesetval->nodeNr == 0))
1618 failed = 1;
1619 break;
1620 case XPATH_BOOLEAN:
1621 failed = !ret->boolval;
1622 break;
1623 case XPATH_NUMBER:
1624 if ((xmlXPathIsNaN(ret->floatval)) ||
1625 (ret->floatval == 0.0))
1626 failed = 1;
1627 break;
1628 case XPATH_STRING:
1629 if ((ret->stringval == NULL) ||
1630 (ret->stringval[0] == 0))
1631 failed = 1;
1632 break;
1633 case XPATH_UNDEFINED:
1634 case XPATH_POINT:
1635 case XPATH_RANGE:
1636 case XPATH_LOCATIONSET:
1637 case XPATH_USERS:
1638 failed = 1;
1639 break;
1640 }
1641 xmlXPathFreeObject(ret);
1642 }
1643 if ((failed) && (test->type == XML_SCHEMATRON_ASSERT))
1644 ctxt->nberrors++;
1645 else if ((!failed) && (test->type == XML_SCHEMATRON_REPORT))
1646 ctxt->nberrors++;
1647
1648 xmlSchematronReportSuccess(ctxt, test, cur, pattern, !failed);
1649
1650 return(!failed);
1651 }
1652
1653 /**
1654 * xmlSchematronValidateDoc:
1655 * @ctxt: the schema validation context
1656 * @instance: the document instace tree
1657 *
1658 * Validate a tree instance against the schematron
1659 *
1660 * Returns 0 in case of success, -1 in case of internal error
1661 * and an error count otherwise.
1662 */
1663 int
xmlSchematronValidateDoc(xmlSchematronValidCtxtPtr ctxt,xmlDocPtr instance)1664 xmlSchematronValidateDoc(xmlSchematronValidCtxtPtr ctxt, xmlDocPtr instance)
1665 {
1666 xmlNodePtr cur, root;
1667 xmlSchematronPatternPtr pattern;
1668 xmlSchematronRulePtr rule;
1669 xmlSchematronTestPtr test;
1670
1671 if ((ctxt == NULL) || (ctxt->schema == NULL) ||
1672 (ctxt->schema->rules == NULL) || (instance == NULL))
1673 return(-1);
1674 ctxt->nberrors = 0;
1675 root = xmlDocGetRootElement(instance);
1676 if (root == NULL) {
1677 TODO
1678 ctxt->nberrors++;
1679 return(1);
1680 }
1681 if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) ||
1682 (ctxt->flags == 0)) {
1683 /*
1684 * we are just trying to assert the validity of the document,
1685 * speed primes over the output, run in a single pass
1686 */
1687 cur = root;
1688 while (cur != NULL) {
1689 rule = ctxt->schema->rules;
1690 while (rule != NULL) {
1691 if (xmlPatternMatch(rule->pattern, cur) == 1) {
1692 test = rule->tests;
1693 while (test != NULL) {
1694 xmlSchematronRunTest(ctxt, test, instance, cur, (xmlSchematronPatternPtr)rule->pattern);
1695 test = test->next;
1696 }
1697 }
1698 rule = rule->next;
1699 }
1700
1701 cur = xmlSchematronNextNode(cur);
1702 }
1703 } else {
1704 /*
1705 * Process all contexts one at a time
1706 */
1707 pattern = ctxt->schema->patterns;
1708
1709 while (pattern != NULL) {
1710 xmlSchematronReportPattern(ctxt, pattern);
1711
1712 /*
1713 * TODO convert the pattern rule to a direct XPath and
1714 * compute directly instead of using the pattern matching
1715 * over the full document...
1716 * Check the exact semantic
1717 */
1718 cur = root;
1719 while (cur != NULL) {
1720 rule = pattern->rules;
1721 while (rule != NULL) {
1722 if (xmlPatternMatch(rule->pattern, cur) == 1) {
1723 test = rule->tests;
1724 while (test != NULL) {
1725 xmlSchematronRunTest(ctxt, test, instance, cur, pattern);
1726 test = test->next;
1727 }
1728 }
1729 rule = rule->patnext;
1730 }
1731
1732 cur = xmlSchematronNextNode(cur);
1733 }
1734 pattern = pattern->next;
1735 }
1736 }
1737 return(ctxt->nberrors);
1738 }
1739
1740 #ifdef STANDALONE
1741 int
main(void)1742 main(void)
1743 {
1744 int ret;
1745 xmlDocPtr instance;
1746 xmlSchematronParserCtxtPtr pctxt;
1747 xmlSchematronValidCtxtPtr vctxt;
1748 xmlSchematronPtr schema = NULL;
1749
1750 pctxt = xmlSchematronNewParserCtxt("tst.sct");
1751 if (pctxt == NULL) {
1752 fprintf(stderr, "failed to build schematron parser\n");
1753 } else {
1754 schema = xmlSchematronParse(pctxt);
1755 if (schema == NULL) {
1756 fprintf(stderr, "failed to compile schematron\n");
1757 }
1758 xmlSchematronFreeParserCtxt(pctxt);
1759 }
1760 instance = xmlReadFile("tst.sct", NULL,
1761 XML_PARSE_NOENT | XML_PARSE_NOCDATA);
1762 if (instance == NULL) {
1763 fprintf(stderr, "failed to parse instance\n");
1764 }
1765 if ((schema != NULL) && (instance != NULL)) {
1766 vctxt = xmlSchematronNewValidCtxt(schema);
1767 if (vctxt == NULL) {
1768 fprintf(stderr, "failed to build schematron validator\n");
1769 } else {
1770 ret = xmlSchematronValidateDoc(vctxt, instance);
1771 xmlSchematronFreeValidCtxt(vctxt);
1772 }
1773 }
1774 xmlSchematronFree(schema);
1775 xmlFreeDoc(instance);
1776
1777 xmlCleanupParser();
1778 xmlMemoryDump();
1779
1780 return (0);
1781 }
1782 #endif
1783 #define bottom_schematron
1784 #include "elfgcchack.h"
1785 #endif /* LIBXML_SCHEMATRON_ENABLED */
1786