blob: 832ff416c2187dc2c88eae702f9fcee5a0ea63d3 [file] [log] [blame]
Daniel Veillarded6c5492005-07-23 15:00:22 +00001/*
Daniel Veillarde70375c2005-07-30 21:09:12 +00002 * schematron.c : implementation of the Schematron schema validity checking
Daniel Veillarded6c5492005-07-23 15:00:22 +00003 *
4 * See Copyright for the status of this software.
5 *
Daniel Veillarde70375c2005-07-30 21:09:12 +00006 * 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
Daniel Veillarded6c5492005-07-23 15:00:22 +000021 */
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
Daniel Veillarde70375c2005-07-30 21:09:12 +000039#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
Daniel Veillarded6c5492005-07-23 15:00:22 +000044static const xmlChar *xmlSchematronNs = XML_SCHEMATRON_NS;
Daniel Veillarde70375c2005-07-30 21:09:12 +000045static const xmlChar *xmlOldSchematronNs = SCT_OLD_NS;
Daniel Veillarded6c5492005-07-23 15:00:22 +000046
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)) && \
Daniel Veillarde70375c2005-07-30 21:09:12 +000051 ((xmlStrEqual(node->ns->href, xmlSchematronNs)) || \
52 (xmlStrEqual(node->ns->href, xmlOldSchematronNs))))
Daniel Veillarded6c5492005-07-23 15:00:22 +000053
54#define NEXT_SCHEMATRON(node) \
55 while (node != NULL) { \
56 if ((node->type == XML_ELEMENT_NODE ) && (node->ns != NULL) && \
Daniel Veillarde70375c2005-07-30 21:09:12 +000057 ((xmlStrEqual(node->ns->href, xmlSchematronNs)) || \
58 (xmlStrEqual(node->ns->href, xmlOldSchematronNs)))) \
Daniel Veillarded6c5492005-07-23 15:00:22 +000059 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
Daniel Veillarde70375c2005-07-30 21:09:12 +000073typedef enum {
74 XML_SCHEMATRON_ASSERT=1,
75 XML_SCHEMATRON_REPORT=2
76} xmlSchematronTestType;
77
Daniel Veillarded6c5492005-07-23 15:00:22 +000078/**
79 * _xmlSchematronTest:
80 *
81 * A Schematrons test, either an assert or a report
82 */
83typedef struct _xmlSchematronTest xmlSchematronTest;
84typedef xmlSchematronTest *xmlSchematronTestPtr;
85struct _xmlSchematronTest {
86 xmlSchematronTestPtr next; /* the next test in the list */
Daniel Veillarde70375c2005-07-30 21:09:12 +000087 xmlSchematronTestType type; /* the test type */
Daniel Veillarded6c5492005-07-23 15:00:22 +000088 xmlNodePtr node; /* the node in the tree */
89 xmlChar *test; /* the expression to test */
90 xmlXPathCompExprPtr comp; /* the compiled expression */
Daniel Veillardd4501d72005-07-24 14:27:16 +000091 xmlChar *report; /* the message to report */
Daniel Veillarded6c5492005-07-23 15:00:22 +000092};
93
94/**
95 * _xmlSchematronRule:
96 *
97 * A Schematrons rule
98 */
99typedef struct _xmlSchematronRule xmlSchematronRule;
100typedef xmlSchematronRule *xmlSchematronRulePtr;
101struct _xmlSchematronRule {
102 xmlSchematronRulePtr next; /* the next rule in the list */
Daniel Veillarde70375c2005-07-30 21:09:12 +0000103 xmlSchematronRulePtr patnext;/* the next rule in the pattern list */
Daniel Veillarded6c5492005-07-23 15:00:22 +0000104 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 */
Daniel Veillardd4501d72005-07-24 14:27:16 +0000108 xmlChar *report; /* the message to report */
Daniel Veillarded6c5492005-07-23 15:00:22 +0000109};
110
111/**
Daniel Veillarde70375c2005-07-30 21:09:12 +0000112 * _xmlSchematronPattern:
113 *
114 * A Schematrons pattern
115 */
116typedef struct _xmlSchematronPattern xmlSchematronPattern;
117typedef xmlSchematronPattern *xmlSchematronPatternPtr;
118struct _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/**
Daniel Veillarded6c5492005-07-23 15:00:22 +0000125 * _xmlSchematron:
126 *
127 * A Schematrons definition
128 */
129struct _xmlSchematron {
130 const xmlChar *name; /* schema name */
131 int preserve; /* was the document preserved 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 */
Daniel Veillarde70375c2005-07-30 21:09:12 +0000143 xmlSchematronPatternPtr patterns;/* the patterns found */
Daniel Veillarded6c5492005-07-23 15:00:22 +0000144 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 */
155struct _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
173struct _xmlSchematronParserCtxt {
174 int type;
175 const xmlChar *URL;
176 xmlDocPtr doc;
177 int preserve; /* Whether the doc should be freed */
178 const char *buffer;
179 int size;
180
181 xmlDictPtr dict; /* dictionnary for interned string names */
182
183 int nberrors;
184 int err;
185 xmlXPathContextPtr xctxt; /* the XPath context used for compilation */
186 xmlSchematronPtr schema;
187
188 int nbNamespaces; /* number of namespaces in the array */
189 int maxNamespaces; /* size of the array */
190 const xmlChar **namespaces; /* the array of namespaces */
191
192 int nbIncludes; /* number of includes in the array */
193 int maxIncludes; /* size of the array */
194 xmlNodePtr *includes; /* the array of includes */
195
196 /* error rreporting data */
197 void *userData; /* user specific data block */
198 xmlSchematronValidityErrorFunc error;/* the callback in case of errors */
199 xmlSchematronValidityWarningFunc warning;/* callback in case of warning */
200 xmlStructuredErrorFunc serror; /* the structured function */
201
202};
203
204#define XML_STRON_CTXT_PARSER 1
205#define XML_STRON_CTXT_VALIDATOR 2
206
207/************************************************************************
208 * *
209 * Error reporting *
210 * *
211 ************************************************************************/
212
213/**
214 * xmlSchematronPErrMemory:
215 * @node: a context node
216 * @extra: extra informations
217 *
218 * Handle an out of memory condition
219 */
220static void
221xmlSchematronPErrMemory(xmlSchematronParserCtxtPtr ctxt,
222 const char *extra, xmlNodePtr node)
223{
224 if (ctxt != NULL)
225 ctxt->nberrors++;
226 __xmlSimpleError(XML_FROM_SCHEMASP, XML_ERR_NO_MEMORY, node, NULL,
227 extra);
228}
229
230/**
231 * xmlSchematronPErr:
232 * @ctxt: the parsing context
233 * @node: the context node
234 * @error: the error code
235 * @msg: the error message
236 * @str1: extra data
237 * @str2: extra data
238 *
239 * Handle a parser error
240 */
241static void
242xmlSchematronPErr(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr node, int error,
243 const char *msg, const xmlChar * str1, const xmlChar * str2)
244{
245 xmlGenericErrorFunc channel = NULL;
246 xmlStructuredErrorFunc schannel = NULL;
247 void *data = NULL;
248
249 if (ctxt != NULL) {
250 ctxt->nberrors++;
251 channel = ctxt->error;
252 data = ctxt->userData;
253 schannel = ctxt->serror;
254 }
255 __xmlRaiseError(schannel, channel, data, ctxt, node, XML_FROM_SCHEMASP,
256 error, XML_ERR_ERROR, NULL, 0,
257 (const char *) str1, (const char *) str2, NULL, 0, 0,
258 msg, str1, str2);
259}
260
261/**
262 * xmlSchematronVTypeErrMemory:
263 * @node: a context node
264 * @extra: extra informations
265 *
266 * Handle an out of memory condition
267 */
268static void
269xmlSchematronVErrMemory(xmlSchematronValidCtxtPtr ctxt,
270 const char *extra, xmlNodePtr node)
271{
272 if (ctxt != NULL) {
273 ctxt->nberrors++;
274 ctxt->err = XML_SCHEMAV_INTERNAL;
275 }
276 __xmlSimpleError(XML_FROM_SCHEMASV, XML_ERR_NO_MEMORY, node, NULL,
277 extra);
278}
279
280/************************************************************************
281 * *
282 * Parsing and compilation of the Schematrontrons *
283 * *
284 ************************************************************************/
285
286/**
287 * xmlSchematronAddTest:
288 * @ctxt: the schema parsing context
Daniel Veillarde70375c2005-07-30 21:09:12 +0000289 * @type: the type of test
290 * @rule: the parent rule
Daniel Veillarded6c5492005-07-23 15:00:22 +0000291 * @node: the node hosting the test
Daniel Veillarde70375c2005-07-30 21:09:12 +0000292 * @test: the associated test
Daniel Veillardd4501d72005-07-24 14:27:16 +0000293 * @report: the associated report string
Daniel Veillarded6c5492005-07-23 15:00:22 +0000294 *
295 * Add a test to a schematron
296 *
297 * Returns the new pointer or NULL in case of error
298 */
299static xmlSchematronTestPtr
Daniel Veillarde70375c2005-07-30 21:09:12 +0000300xmlSchematronAddTest(xmlSchematronParserCtxtPtr ctxt,
301 xmlSchematronTestType type,
Daniel Veillarded6c5492005-07-23 15:00:22 +0000302 xmlSchematronRulePtr rule,
Daniel Veillardd4501d72005-07-24 14:27:16 +0000303 xmlNodePtr node, xmlChar *test, xmlChar *report)
Daniel Veillarded6c5492005-07-23 15:00:22 +0000304{
305 xmlSchematronTestPtr ret;
306 xmlXPathCompExprPtr comp;
307
308 if ((ctxt == NULL) || (rule == NULL) || (node == NULL) ||
309 (test == NULL))
310 return(NULL);
311
312 /*
313 * try first to compile the test expression
314 */
315 comp = xmlXPathCtxtCompile(ctxt->xctxt, test);
316 if (comp == NULL) {
317 xmlSchematronPErr(ctxt, node,
318 XML_SCHEMAP_NOROOT,
319 "Failed to compile test expression %s",
320 test, NULL);
321 return(NULL);
322 }
323
324 ret = (xmlSchematronTestPtr) xmlMalloc(sizeof(xmlSchematronTest));
325 if (ret == NULL) {
326 xmlSchematronPErrMemory(ctxt, "allocating schema test", node);
327 return (NULL);
328 }
329 memset(ret, 0, sizeof(xmlSchematronTest));
330 ret->type = type;
331 ret->node = node;
332 ret->test = test;
333 ret->comp = comp;
Daniel Veillardd4501d72005-07-24 14:27:16 +0000334 ret->report = report;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000335 ret->next = rule->tests;
336 rule->tests = ret;
337 return (ret);
338}
339
340/**
341 * xmlSchematronFreeTests:
342 * @tests: a list of tests
343 *
344 * Free a list of tests.
345 */
346static void
347xmlSchematronFreeTests(xmlSchematronTestPtr tests) {
348 xmlSchematronTestPtr next;
349
350 while (tests != NULL) {
351 next = tests->next;
352 if (tests->test != NULL)
353 xmlFree(tests->test);
354 if (tests->comp != NULL)
355 xmlXPathFreeCompExpr(tests->comp);
Daniel Veillardd4501d72005-07-24 14:27:16 +0000356 if (tests->report != NULL)
357 xmlFree(tests->report);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000358 xmlFree(tests);
359 tests = next;
360 }
361}
362
363/**
364 * xmlSchematronAddRule:
365 * @ctxt: the schema parsing context
366 * @schema: a schema structure
367 * @node: the node hosting the rule
368 * @context: the associated context string
Daniel Veillardd4501d72005-07-24 14:27:16 +0000369 * @report: the associated report string
Daniel Veillarded6c5492005-07-23 15:00:22 +0000370 *
371 * Add a rule to a schematron
372 *
373 * Returns the new pointer or NULL in case of error
374 */
375static xmlSchematronRulePtr
376xmlSchematronAddRule(xmlSchematronParserCtxtPtr ctxt, xmlSchematronPtr schema,
Daniel Veillarde70375c2005-07-30 21:09:12 +0000377 xmlSchematronPatternPtr pat, xmlNodePtr node,
378 xmlChar *context, xmlChar *report)
Daniel Veillarded6c5492005-07-23 15:00:22 +0000379{
380 xmlSchematronRulePtr ret;
381 xmlPatternPtr pattern;
382
383 if ((ctxt == NULL) || (schema == NULL) || (node == NULL) ||
384 (context == NULL))
385 return(NULL);
386
387 /*
388 * Try first to compile the pattern
389 */
390 pattern = xmlPatterncompile(context, ctxt->dict, XML_PATTERN_XPATH,
391 ctxt->namespaces);
392 if (pattern == NULL) {
393 xmlSchematronPErr(ctxt, node,
394 XML_SCHEMAP_NOROOT,
395 "Failed to compile context expression %s",
396 context, NULL);
397 }
398
399 ret = (xmlSchematronRulePtr) xmlMalloc(sizeof(xmlSchematronRule));
400 if (ret == NULL) {
401 xmlSchematronPErrMemory(ctxt, "allocating schema rule", node);
402 return (NULL);
403 }
404 memset(ret, 0, sizeof(xmlSchematronRule));
405 ret->node = node;
406 ret->context = context;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000407 ret->pattern = pattern;
Daniel Veillardd4501d72005-07-24 14:27:16 +0000408 ret->report = report;
Daniel Veillarde70375c2005-07-30 21:09:12 +0000409 ret->next = schema->rules;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000410 schema->rules = ret;
Daniel Veillarde70375c2005-07-30 21:09:12 +0000411 ret->patnext = pat->rules;
412 pat->rules = ret;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000413 return (ret);
414}
415
416/**
417 * xmlSchematronFreeRules:
418 * @rules: a list of rules
419 *
420 * Free a list of rules.
421 */
422static void
423xmlSchematronFreeRules(xmlSchematronRulePtr rules) {
424 xmlSchematronRulePtr next;
425
426 while (rules != NULL) {
427 next = rules->next;
428 if (rules->tests)
429 xmlSchematronFreeTests(rules->tests);
430 if (rules->context != NULL)
431 xmlFree(rules->context);
432 if (rules->pattern)
433 xmlFreePattern(rules->pattern);
Daniel Veillardd4501d72005-07-24 14:27:16 +0000434 if (rules->report != NULL)
435 xmlFree(rules->report);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000436 xmlFree(rules);
437 rules = next;
438 }
439}
440
441/**
Daniel Veillarde70375c2005-07-30 21:09:12 +0000442 * xmlSchematronAddPattern:
443 * @ctxt: the schema parsing context
444 * @schema: a schema structure
445 * @node: the node hosting the pattern
446 * @id: the id or name of the pattern
447 *
448 * Add a pattern to a schematron
449 *
450 * Returns the new pointer or NULL in case of error
451 */
452static xmlSchematronPatternPtr
453xmlSchematronAddPattern(xmlSchematronParserCtxtPtr ctxt,
454 xmlSchematronPtr schema, xmlNodePtr node, xmlChar *name)
455{
456 xmlSchematronPatternPtr ret;
457
458 if ((ctxt == NULL) || (schema == NULL) || (node == NULL) || (name == NULL))
459 return(NULL);
460
461 ret = (xmlSchematronPatternPtr) xmlMalloc(sizeof(xmlSchematronPattern));
462 if (ret == NULL) {
463 xmlSchematronPErrMemory(ctxt, "allocating schema pattern", node);
464 return (NULL);
465 }
466 memset(ret, 0, sizeof(xmlSchematronPattern));
467 ret->name = name;
468 ret->next = schema->patterns;
469 schema->patterns = ret;
470 return (ret);
471}
472
473/**
474 * xmlSchematronFreePatterns:
475 * @patterns: a list of patterns
476 *
477 * Free a list of patterns.
478 */
479static void
480xmlSchematronFreePatterns(xmlSchematronPatternPtr patterns) {
481 xmlSchematronPatternPtr next;
482
483 while (patterns != NULL) {
484 next = patterns->next;
485 if (patterns->name != NULL)
486 xmlFree(patterns->name);
487 xmlFree(patterns);
488 patterns = next;
489 }
490}
491
492/**
Daniel Veillarded6c5492005-07-23 15:00:22 +0000493 * xmlSchematronNewSchematron:
494 * @ctxt: a schema validation context
495 *
496 * Allocate a new Schematron structure.
497 *
498 * Returns the newly allocated structure or NULL in case or error
499 */
500static xmlSchematronPtr
501xmlSchematronNewSchematron(xmlSchematronParserCtxtPtr ctxt)
502{
503 xmlSchematronPtr ret;
504
505 ret = (xmlSchematronPtr) xmlMalloc(sizeof(xmlSchematron));
506 if (ret == NULL) {
507 xmlSchematronPErrMemory(ctxt, "allocating schema", NULL);
508 return (NULL);
509 }
510 memset(ret, 0, sizeof(xmlSchematron));
511 ret->dict = ctxt->dict;
512 xmlDictReference(ret->dict);
513
514 return (ret);
515}
516
517/**
518 * xmlSchematronFree:
519 * @schema: a schema structure
520 *
521 * Deallocate a Schematron structure.
522 */
523void
524xmlSchematronFree(xmlSchematronPtr schema)
525{
526 if (schema == NULL)
527 return;
528
529 if ((schema->doc != NULL) && (!(schema->preserve)))
530 xmlFreeDoc(schema->doc);
531
532 if (schema->namespaces != NULL)
533 xmlFree(schema->namespaces);
534
535 xmlSchematronFreeRules(schema->rules);
Daniel Veillarde70375c2005-07-30 21:09:12 +0000536 xmlSchematronFreePatterns(schema->patterns);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000537 xmlDictFree(schema->dict);
538 xmlFree(schema);
539}
540
541/**
542 * xmlSchematronNewParserCtxt:
543 * @URL: the location of the schema
544 *
545 * Create an XML Schematrons parse context for that file/resource expected
546 * to contain an XML Schematrons file.
547 *
548 * Returns the parser context or NULL in case of error
549 */
550xmlSchematronParserCtxtPtr
551xmlSchematronNewParserCtxt(const char *URL)
552{
553 xmlSchematronParserCtxtPtr ret;
554
555 if (URL == NULL)
556 return (NULL);
557
558 ret =
559 (xmlSchematronParserCtxtPtr)
560 xmlMalloc(sizeof(xmlSchematronParserCtxt));
561 if (ret == NULL) {
562 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
563 NULL);
564 return (NULL);
565 }
566 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
567 ret->type = XML_STRON_CTXT_PARSER;
568 ret->dict = xmlDictCreate();
569 ret->URL = xmlDictLookup(ret->dict, (const xmlChar *) URL, -1);
Daniel Veillard24505b02005-07-28 23:49:35 +0000570 ret->includes = NULL;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000571 ret->xctxt = xmlXPathNewContext(NULL);
572 if (ret->xctxt == NULL) {
573 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
574 NULL);
575 xmlSchematronFreeParserCtxt(ret);
576 return (NULL);
577 }
578 ret->xctxt->flags = XML_XPATH_CHECKNS;
579 return (ret);
580}
581
582/**
583 * xmlSchematronNewMemParserCtxt:
584 * @buffer: a pointer to a char array containing the schemas
585 * @size: the size of the array
586 *
587 * Create an XML Schematrons parse context for that memory buffer expected
588 * to contain an XML Schematrons file.
589 *
590 * Returns the parser context or NULL in case of error
591 */
592xmlSchematronParserCtxtPtr
593xmlSchematronNewMemParserCtxt(const char *buffer, int size)
594{
595 xmlSchematronParserCtxtPtr ret;
596
597 if ((buffer == NULL) || (size <= 0))
598 return (NULL);
599
600 ret =
601 (xmlSchematronParserCtxtPtr)
602 xmlMalloc(sizeof(xmlSchematronParserCtxt));
603 if (ret == NULL) {
604 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
605 NULL);
606 return (NULL);
607 }
608 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
609 ret->buffer = buffer;
610 ret->size = size;
611 ret->dict = xmlDictCreate();
612 ret->xctxt = xmlXPathNewContext(NULL);
613 if (ret->xctxt == NULL) {
614 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
615 NULL);
616 xmlSchematronFreeParserCtxt(ret);
617 return (NULL);
618 }
619 return (ret);
620}
621
622/**
623 * xmlSchematronNewDocParserCtxt:
624 * @doc: a preparsed document tree
625 *
626 * Create an XML Schematrons parse context for that document.
627 * NB. The document may be modified during the parsing process.
628 *
629 * Returns the parser context or NULL in case of error
630 */
631xmlSchematronParserCtxtPtr
632xmlSchematronNewDocParserCtxt(xmlDocPtr doc)
633{
634 xmlSchematronParserCtxtPtr ret;
635
636 if (doc == NULL)
637 return (NULL);
638
639 ret =
640 (xmlSchematronParserCtxtPtr)
641 xmlMalloc(sizeof(xmlSchematronParserCtxt));
642 if (ret == NULL) {
643 xmlSchematronPErrMemory(NULL, "allocating schema parser context",
644 NULL);
645 return (NULL);
646 }
647 memset(ret, 0, sizeof(xmlSchematronParserCtxt));
648 ret->doc = doc;
649 ret->dict = xmlDictCreate();
650 /* The application has responsibility for the document */
651 ret->preserve = 1;
652 ret->xctxt = xmlXPathNewContext(doc);
653 if (ret->xctxt == NULL) {
654 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
655 NULL);
656 xmlSchematronFreeParserCtxt(ret);
657 return (NULL);
658 }
659
660 return (ret);
661}
662
663/**
664 * xmlSchematronFreeParserCtxt:
665 * @ctxt: the schema parser context
666 *
667 * Free the resources associated to the schema parser context
668 */
669void
670xmlSchematronFreeParserCtxt(xmlSchematronParserCtxtPtr ctxt)
671{
672 if (ctxt == NULL)
673 return;
674 if (ctxt->doc != NULL && !ctxt->preserve)
675 xmlFreeDoc(ctxt->doc);
676 if (ctxt->xctxt != NULL) {
677 xmlXPathFreeContext(ctxt->xctxt);
678 }
679 if (ctxt->namespaces != NULL)
680 xmlFree(ctxt->namespaces);
681 xmlDictFree(ctxt->dict);
682 xmlFree(ctxt);
683}
684
685/**
686 * xmlSchematronPushInclude:
687 * @ctxt: the schema parser context
688 * @doc: the included document
689 * @cur: the current include node
690 *
691 * Add an included document
692 */
693static void
694xmlSchematronPushInclude(xmlSchematronParserCtxtPtr ctxt,
695 xmlDocPtr doc, xmlNodePtr cur)
696{
697 if (ctxt->includes == NULL) {
698 ctxt->maxIncludes = 10;
699 ctxt->includes = (xmlNodePtr *)
700 xmlMalloc(ctxt->maxIncludes * 2 * sizeof(xmlNodePtr));
701 if (ctxt->includes == NULL) {
702 xmlSchematronPErrMemory(NULL, "allocating parser includes",
703 NULL);
704 return;
705 }
706 ctxt->nbIncludes = 0;
707 } else if (ctxt->nbIncludes + 2 >= ctxt->maxIncludes) {
708 xmlNodePtr *tmp;
709
710 tmp = (xmlNodePtr *)
711 xmlRealloc(ctxt->includes, ctxt->maxIncludes * 4 *
712 sizeof(xmlNodePtr));
713 if (tmp == NULL) {
714 xmlSchematronPErrMemory(NULL, "allocating parser includes",
715 NULL);
716 return;
717 }
718 ctxt->includes = tmp;
719 ctxt->maxIncludes *= 2;
720 }
721 ctxt->includes[2 * ctxt->nbIncludes] = cur;
722 ctxt->includes[2 * ctxt->nbIncludes + 1] = (xmlNodePtr) doc;
723 ctxt->nbIncludes++;
724}
725
726/**
727 * xmlSchematronPopInclude:
728 * @ctxt: the schema parser context
729 *
730 * Pop an include level. The included document is being freed
731 *
732 * Returns the node immediately following the include or NULL if the
733 * include list was empty.
734 */
735static xmlNodePtr
736xmlSchematronPopInclude(xmlSchematronParserCtxtPtr ctxt)
737{
738 xmlDocPtr doc;
739 xmlNodePtr ret;
740
741 if (ctxt->nbIncludes <= 0)
742 return(NULL);
743 ctxt->nbIncludes--;
744 doc = (xmlDocPtr) ctxt->includes[2 * ctxt->nbIncludes + 1];
745 ret = ctxt->includes[2 * ctxt->nbIncludes];
746 xmlFreeDoc(doc);
747 if (ret != NULL)
748 ret = ret->next;
749 if (ret == NULL)
750 return(xmlSchematronPopInclude(ctxt));
751 return(ret);
752}
753
754/**
755 * xmlSchematronAddNamespace:
756 * @ctxt: the schema parser context
757 * @prefix: the namespace prefix
758 * @ns: the namespace name
759 *
760 * Add a namespace definition in the context
761 */
762static void
763xmlSchematronAddNamespace(xmlSchematronParserCtxtPtr ctxt,
764 const xmlChar *prefix, const xmlChar *ns)
765{
766 if (ctxt->namespaces == NULL) {
767 ctxt->maxNamespaces = 10;
768 ctxt->namespaces = (const xmlChar **)
769 xmlMalloc(ctxt->maxNamespaces * 2 * sizeof(const xmlChar *));
770 if (ctxt->namespaces == NULL) {
771 xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
772 NULL);
773 return;
774 }
775 ctxt->nbNamespaces = 0;
776 } else if (ctxt->nbNamespaces + 2 >= ctxt->maxNamespaces) {
777 const xmlChar **tmp;
778
779 tmp = (const xmlChar **)
780 xmlRealloc(ctxt->namespaces, ctxt->maxNamespaces * 4 *
781 sizeof(const xmlChar *));
782 if (tmp == NULL) {
783 xmlSchematronPErrMemory(NULL, "allocating parser namespaces",
784 NULL);
785 return;
786 }
787 ctxt->namespaces = tmp;
788 ctxt->maxNamespaces *= 2;
789 }
790 ctxt->namespaces[2 * ctxt->nbNamespaces] =
791 xmlDictLookup(ctxt->dict, ns, -1);
792 ctxt->namespaces[2 * ctxt->nbNamespaces + 1] =
793 xmlDictLookup(ctxt->dict, prefix, -1);
794 ctxt->nbNamespaces++;
795 ctxt->namespaces[2 * ctxt->nbNamespaces] = NULL;
796 ctxt->namespaces[2 * ctxt->nbNamespaces + 1] = NULL;
797
798}
799
800/**
801 * xmlSchematronParseRule:
802 * @ctxt: a schema validation context
803 * @rule: the rule node
804 *
805 * parse a rule element
806 */
807static void
Daniel Veillarde70375c2005-07-30 21:09:12 +0000808xmlSchematronParseRule(xmlSchematronParserCtxtPtr ctxt,
809 xmlSchematronPatternPtr pattern,
810 xmlNodePtr rule)
Daniel Veillarded6c5492005-07-23 15:00:22 +0000811{
812 xmlNodePtr cur;
813 int nbChecks = 0;
814 xmlChar *test;
815 xmlChar *context;
Daniel Veillardd4501d72005-07-24 14:27:16 +0000816 xmlChar *report;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000817 xmlSchematronRulePtr ruleptr;
818 xmlSchematronTestPtr testptr;
819
820 if ((ctxt == NULL) || (rule == NULL)) return;
821
822 context = xmlGetNoNsProp(rule, BAD_CAST "context");
823 if (context == NULL) {
824 xmlSchematronPErr(ctxt, rule,
825 XML_SCHEMAP_NOROOT,
826 "rule has no context attribute",
827 NULL, NULL);
828 return;
829 } else if (context[0] == 0) {
830 xmlSchematronPErr(ctxt, rule,
831 XML_SCHEMAP_NOROOT,
832 "rule has an empty context attribute",
833 NULL, NULL);
834 xmlFree(context);
835 return;
836 } else {
Daniel Veillarde70375c2005-07-30 21:09:12 +0000837 ruleptr = xmlSchematronAddRule(ctxt, ctxt->schema, pattern,
838 rule, context, NULL);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000839 if (ruleptr == NULL) {
840 xmlFree(context);
841 return;
842 }
843 }
844
845 cur = rule->children;
846 NEXT_SCHEMATRON(cur);
847 while (cur != NULL) {
848 if (IS_SCHEMATRON(cur, "assert")) {
849 nbChecks++;
850 test = xmlGetNoNsProp(cur, BAD_CAST "test");
851 if (test == NULL) {
852 xmlSchematronPErr(ctxt, cur,
853 XML_SCHEMAP_NOROOT,
854 "assert has no test attribute",
855 NULL, NULL);
856 } else if (test[0] == 0) {
857 xmlSchematronPErr(ctxt, cur,
858 XML_SCHEMAP_NOROOT,
859 "assert has an empty test attribute",
860 NULL, NULL);
861 xmlFree(test);
862 } else {
Daniel Veillardd4501d72005-07-24 14:27:16 +0000863 /* TODO will need dynamic processing instead */
864 report = xmlNodeGetContent(cur);
865
Daniel Veillarded6c5492005-07-23 15:00:22 +0000866 testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_ASSERT,
Daniel Veillardd4501d72005-07-24 14:27:16 +0000867 ruleptr, cur, test, report);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000868 if (testptr == NULL)
869 xmlFree(test);
870 }
871 } else if (IS_SCHEMATRON(cur, "report")) {
872 nbChecks++;
873 test = xmlGetNoNsProp(cur, BAD_CAST "test");
874 if (test == NULL) {
875 xmlSchematronPErr(ctxt, cur,
876 XML_SCHEMAP_NOROOT,
877 "assert has no test attribute",
878 NULL, NULL);
879 } else if (test[0] == 0) {
880 xmlSchematronPErr(ctxt, cur,
881 XML_SCHEMAP_NOROOT,
882 "assert has an empty test attribute",
883 NULL, NULL);
884 xmlFree(test);
885 } else {
Daniel Veillardd4501d72005-07-24 14:27:16 +0000886 /* TODO will need dynamic processing instead */
887 report = xmlNodeGetContent(cur);
888
Daniel Veillarded6c5492005-07-23 15:00:22 +0000889 testptr = xmlSchematronAddTest(ctxt, XML_SCHEMATRON_REPORT,
Daniel Veillardd4501d72005-07-24 14:27:16 +0000890 ruleptr, cur, test, report);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000891 if (testptr == NULL)
892 xmlFree(test);
893 }
894 } else {
895 xmlSchematronPErr(ctxt, cur,
896 XML_SCHEMAP_NOROOT,
897 "Expecting an assert or a report element instead of %s",
898 cur->name, NULL);
899 }
900 cur = cur->next;
901 NEXT_SCHEMATRON(cur);
902 }
903 if (nbChecks == 0) {
904 xmlSchematronPErr(ctxt, rule,
905 XML_SCHEMAP_NOROOT,
906 "rule has no assert nor report element", NULL, NULL);
907 }
908}
909
910/**
911 * xmlSchematronParsePattern:
912 * @ctxt: a schema validation context
913 * @pat: the pattern node
914 *
915 * parse a pattern element
916 */
917static void
918xmlSchematronParsePattern(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr pat)
919{
920 xmlNodePtr cur;
Daniel Veillarde70375c2005-07-30 21:09:12 +0000921 xmlSchematronPatternPtr pattern;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000922 int nbRules = 0;
Daniel Veillarde70375c2005-07-30 21:09:12 +0000923 xmlChar *id;
Daniel Veillarded6c5492005-07-23 15:00:22 +0000924
925 if ((ctxt == NULL) || (pat == NULL)) return;
926
Daniel Veillarde70375c2005-07-30 21:09:12 +0000927 id = xmlGetNoNsProp(pat, BAD_CAST "id");
928 if (id == NULL) {
929 id = xmlGetNoNsProp(pat, BAD_CAST "name");
930 }
931 pattern = xmlSchematronAddPattern(ctxt, ctxt->schema, pat, id);
932 if (pattern == NULL) {
933 if (id != NULL)
934 xmlFree(id);
935 return;
936 }
Daniel Veillarded6c5492005-07-23 15:00:22 +0000937 cur = pat->children;
938 NEXT_SCHEMATRON(cur);
939 while (cur != NULL) {
940 if (IS_SCHEMATRON(cur, "rule")) {
Daniel Veillarde70375c2005-07-30 21:09:12 +0000941 xmlSchematronParseRule(ctxt, pattern, cur);
Daniel Veillarded6c5492005-07-23 15:00:22 +0000942 nbRules++;
943 } else {
944 xmlSchematronPErr(ctxt, cur,
945 XML_SCHEMAP_NOROOT,
946 "Expecting a rule element instead of %s", cur->name, NULL);
947 }
948 cur = cur->next;
949 NEXT_SCHEMATRON(cur);
950 }
951 if (nbRules == 0) {
952 xmlSchematronPErr(ctxt, pat,
953 XML_SCHEMAP_NOROOT,
954 "Pattern has no rule element", NULL, NULL);
955 }
956}
957
958/**
959 * xmlSchematronLoadInclude:
960 * @ctxt: a schema validation context
961 * @cur: the include element
962 *
963 * Load the include document, Push the current pointer
964 *
965 * Returns the updated node pointer
966 */
967static xmlNodePtr
968xmlSchematronLoadInclude(xmlSchematronParserCtxtPtr ctxt, xmlNodePtr cur)
969{
970 xmlNodePtr ret = NULL;
971 xmlDocPtr doc = NULL;
972 xmlChar *href = NULL;
973 xmlChar *base = NULL;
974 xmlChar *URI = NULL;
975
976 if ((ctxt == NULL) || (cur == NULL))
977 return(NULL);
978
979 href = xmlGetNoNsProp(cur, BAD_CAST "href");
980 if (href == NULL) {
981 xmlSchematronPErr(ctxt, cur,
982 XML_SCHEMAP_NOROOT,
983 "Include has no href attribute", NULL, NULL);
984 return(cur->next);
985 }
986
987 /* do the URI base composition, load and find the root */
988 base = xmlNodeGetBase(cur->doc, cur);
989 URI = xmlBuildURI(href, base);
990 doc = xmlReadFile((const char *) URI, NULL, SCHEMATRON_PARSE_OPTIONS);
991 if (doc == NULL) {
992 xmlSchematronPErr(ctxt, cur,
993 XML_SCHEMAP_FAILED_LOAD,
994 "could not load include '%s'.\n",
995 URI, NULL);
996 goto done;
997 }
998 ret = xmlDocGetRootElement(doc);
999 if (ret == NULL) {
1000 xmlSchematronPErr(ctxt, cur,
1001 XML_SCHEMAP_FAILED_LOAD,
1002 "could not find root from include '%s'.\n",
1003 URI, NULL);
1004 goto done;
1005 }
1006
1007 /* Success, push the include for rollback on exit */
1008 xmlSchematronPushInclude(ctxt, doc, cur);
1009
1010done:
1011 if (ret == NULL) {
1012 if (doc != NULL)
1013 xmlFreeDoc(doc);
1014 }
1015 if (href == NULL)
1016 xmlFree(href);
1017 if (base == NULL)
1018 xmlFree(base);
1019 if (URI == NULL)
1020 xmlFree(URI);
1021 return(ret);
1022}
1023
1024/**
1025 * xmlSchematronParse:
1026 * @ctxt: a schema validation context
1027 *
1028 * parse a schema definition resource and build an internal
1029 * XML Shema struture which can be used to validate instances.
1030 *
1031 * Returns the internal XML Schematron structure built from the resource or
1032 * NULL in case of error
1033 */
1034xmlSchematronPtr
1035xmlSchematronParse(xmlSchematronParserCtxtPtr ctxt)
1036{
1037 xmlSchematronPtr ret = NULL;
1038 xmlDocPtr doc;
1039 xmlNodePtr root, cur;
1040 int preserve = 0;
1041
1042 if (ctxt == NULL)
1043 return (NULL);
1044
1045 ctxt->nberrors = 0;
1046
1047 /*
1048 * First step is to parse the input document into an DOM/Infoset
1049 */
1050 if (ctxt->URL != NULL) {
1051 doc = xmlReadFile((const char *) ctxt->URL, NULL,
1052 SCHEMATRON_PARSE_OPTIONS);
1053 if (doc == NULL) {
1054 xmlSchematronPErr(ctxt, NULL,
1055 XML_SCHEMAP_FAILED_LOAD,
1056 "xmlSchematronParse: could not load '%s'.\n",
1057 ctxt->URL, NULL);
1058 return (NULL);
1059 }
1060 } else if (ctxt->buffer != NULL) {
1061 doc = xmlReadMemory(ctxt->buffer, ctxt->size, NULL, NULL,
1062 SCHEMATRON_PARSE_OPTIONS);
1063 if (doc == NULL) {
1064 xmlSchematronPErr(ctxt, NULL,
1065 XML_SCHEMAP_FAILED_PARSE,
1066 "xmlSchematronParse: could not parse.\n",
1067 NULL, NULL);
1068 return (NULL);
1069 }
1070 doc->URL = xmlStrdup(BAD_CAST "in_memory_buffer");
1071 ctxt->URL = xmlDictLookup(ctxt->dict, BAD_CAST "in_memory_buffer", -1);
1072 } else if (ctxt->doc != NULL) {
1073 doc = ctxt->doc;
1074 preserve = 1;
1075 } else {
1076 xmlSchematronPErr(ctxt, NULL,
1077 XML_SCHEMAP_NOTHING_TO_PARSE,
1078 "xmlSchematronParse: could not parse.\n",
1079 NULL, NULL);
1080 return (NULL);
1081 }
1082
1083 /*
1084 * Then extract the root and Schematron parse it
1085 */
1086 root = xmlDocGetRootElement(doc);
1087 if (root == NULL) {
1088 xmlSchematronPErr(ctxt, (xmlNodePtr) doc,
1089 XML_SCHEMAP_NOROOT,
1090 "The schema has no document element.\n", NULL, NULL);
1091 if (!preserve) {
1092 xmlFreeDoc(doc);
1093 }
1094 return (NULL);
1095 }
1096
1097 if (!IS_SCHEMATRON(root, "schema")) {
1098 xmlSchematronPErr(ctxt, root,
1099 XML_SCHEMAP_NOROOT,
1100 "The XML document '%s' is not a XML schematron document",
1101 ctxt->URL, NULL);
1102 goto exit;
1103 }
1104 ret = xmlSchematronNewSchematron(ctxt);
1105 if (ret == NULL)
1106 goto exit;
1107 ctxt->schema = ret;
1108
1109 /*
1110 * scan the schema elements
1111 */
1112 cur = root->children;
1113 NEXT_SCHEMATRON(cur);
1114 if (IS_SCHEMATRON(cur, "title")) {
1115 xmlChar *title = xmlNodeGetContent(cur);
1116 if (title != NULL) {
1117 ret->title = xmlDictLookup(ret->dict, title, -1);
1118 xmlFree(title);
1119 }
1120 cur = cur->next;
1121 NEXT_SCHEMATRON(cur);
1122 }
1123 while (IS_SCHEMATRON(cur, "ns")) {
1124 xmlChar *prefix = xmlGetNoNsProp(cur, BAD_CAST "prefix");
1125 xmlChar *uri = xmlGetNoNsProp(cur, BAD_CAST "uri");
1126 if ((uri == NULL) || (uri[0] == 0)) {
1127 xmlSchematronPErr(ctxt, cur,
1128 XML_SCHEMAP_NOROOT,
1129 "ns element has no uri", NULL, NULL);
1130 }
1131 if ((prefix == NULL) || (prefix[0] == 0)) {
1132 xmlSchematronPErr(ctxt, cur,
1133 XML_SCHEMAP_NOROOT,
1134 "ns element has no prefix", NULL, NULL);
1135 }
1136 if ((prefix) && (uri)) {
1137 xmlXPathRegisterNs(ctxt->xctxt, prefix, uri);
1138 xmlSchematronAddNamespace(ctxt, prefix, uri);
1139 ret->nbNs++;
1140 }
1141 if (uri)
1142 xmlFree(uri);
1143 if (prefix)
1144 xmlFree(prefix);
1145 cur = cur->next;
1146 NEXT_SCHEMATRON(cur);
1147 }
1148 while (cur != NULL) {
1149 if (IS_SCHEMATRON(cur, "pattern")) {
1150 xmlSchematronParsePattern(ctxt, cur);
1151 ret->nbPattern++;
1152 } else {
1153 xmlSchematronPErr(ctxt, cur,
1154 XML_SCHEMAP_NOROOT,
1155 "Expecting a pattern element instead of %s", cur->name, NULL);
1156 }
1157 cur = cur->next;
1158 NEXT_SCHEMATRON(cur);
1159 }
1160 if (ret->nbPattern == 0) {
1161 xmlSchematronPErr(ctxt, root,
1162 XML_SCHEMAP_NOROOT,
1163 "The schematron document '%s' has no pattern",
1164 ctxt->URL, NULL);
1165 goto exit;
1166 }
1167
1168exit:
1169 if (!preserve) {
1170 xmlFreeDoc(doc);
1171 }
1172 if (ctxt->nberrors != 0) {
1173 xmlSchematronFree(ret);
1174 ret = NULL;
1175 } else {
1176 ret->namespaces = ctxt->namespaces;
1177 ret->nbNamespaces = ctxt->nbNamespaces;
1178 ctxt->namespaces = NULL;
1179 }
1180 return (ret);
1181}
1182
1183/************************************************************************
1184 * *
1185 * Schematrontron Reports handler *
1186 * *
1187 ************************************************************************/
1188
Daniel Veillardd4501d72005-07-24 14:27:16 +00001189/**
1190 * xmlSchematronReportOutput:
1191 * @ctxt: the validation context
1192 * @cur: the current node tested
1193 * @msg: the message output
1194 *
1195 * Output part of the report to whatever channel the user selected
1196 */
1197static void
1198xmlSchematronReportOutput(xmlSchematronValidCtxtPtr ctxt ATTRIBUTE_UNUSED,
1199 xmlNodePtr cur ATTRIBUTE_UNUSED,
1200 const char *msg ATTRIBUTE_UNUSED) {
1201 /* TODO */
1202 fprintf(stderr, "%s", msg);
1203}
1204
1205/**
1206 * xmlSchematronReportSuccess:
1207 * @ctxt: the validation context
1208 * @test: the compiled test
1209 * @cur: the current node tested
1210 * @success: boolean value for the result
1211 *
1212 * called from the validation engine when an assert or report test have
1213 * been done.
1214 */
Daniel Veillarded6c5492005-07-23 15:00:22 +00001215static void
1216xmlSchematronReportSuccess(xmlSchematronValidCtxtPtr ctxt,
Daniel Veillardd4501d72005-07-24 14:27:16 +00001217 xmlSchematronTestPtr test, xmlNodePtr cur, int success) {
1218 if ((ctxt == NULL) || (cur == NULL) || (test == NULL))
1219 return;
1220 /* if quiet and not SVRL report only failures */
1221 if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) &&
Daniel Veillarde70375c2005-07-30 21:09:12 +00001222 ((ctxt->flags & XML_SCHEMATRON_OUT_XML) == 0) &&
1223 (((test->type == XML_SCHEMATRON_REPORT) & (!success)) ||
1224 ((test->type == XML_SCHEMATRON_ASSERT) & (success))))
Daniel Veillardd4501d72005-07-24 14:27:16 +00001225 return;
1226 if (ctxt->flags & XML_SCHEMATRON_OUT_XML) {
1227 TODO
1228 } else {
1229 xmlChar *path;
1230 char msg[1000];
1231 long line;
1232 const xmlChar *report;
1233
Daniel Veillarde70375c2005-07-30 21:09:12 +00001234 if (((test->type == XML_SCHEMATRON_REPORT) & (!success)) ||
1235 ((test->type == XML_SCHEMATRON_ASSERT) & (success)))
Daniel Veillardd4501d72005-07-24 14:27:16 +00001236 return;
1237 line = xmlGetLineNo(cur);
1238 path = xmlGetNodePath(cur);
1239 if (path == NULL)
1240 path = (xmlChar *) cur->name;
1241 if ((test->report != NULL) && (test->report[0] != 0))
1242 report = test->report;
1243 else if (test->type == XML_SCHEMATRON_ASSERT) {
1244 report = BAD_CAST "node failed assert";
1245 } else {
1246 report = BAD_CAST "node failed report";
1247 }
Daniel Veillarde70375c2005-07-30 21:09:12 +00001248 snprintf(msg, 999, "%s line %ld: %s\n", (const char *) path,
Daniel Veillardd4501d72005-07-24 14:27:16 +00001249 line, (const char *) report);
1250 xmlSchematronReportOutput(ctxt, cur, &msg[0]);
1251 if ((path != NULL) && (path != (xmlChar *) cur->name))
1252 xmlFree(path);
1253 }
Daniel Veillarded6c5492005-07-23 15:00:22 +00001254}
1255
1256/************************************************************************
1257 * *
1258 * Validation against a Schematrontron *
1259 * *
1260 ************************************************************************/
1261
1262/**
1263 * xmlSchematronNewValidCtxt:
1264 * @schema: a precompiled XML Schematrons
1265 * @options: a set of xmlSchematronValidOptions
1266 *
1267 * Create an XML Schematrons validation context based on the given schema.
1268 *
1269 * Returns the validation context or NULL in case of error
1270 */
1271xmlSchematronValidCtxtPtr
1272xmlSchematronNewValidCtxt(xmlSchematronPtr schema, int options)
1273{
1274 int i;
1275 xmlSchematronValidCtxtPtr ret;
1276
1277 ret =
1278 (xmlSchematronValidCtxtPtr)
1279 xmlMalloc(sizeof(xmlSchematronValidCtxt));
1280 if (ret == NULL) {
1281 xmlSchematronVErrMemory(NULL, "allocating validation context",
1282 NULL);
1283 return (NULL);
1284 }
1285 memset(ret, 0, sizeof(xmlSchematronValidCtxt));
1286 ret->type = XML_STRON_CTXT_VALIDATOR;
1287 ret->schema = schema;
1288 ret->xctxt = xmlXPathNewContext(NULL);
Daniel Veillardd4501d72005-07-24 14:27:16 +00001289 ret->flags = options;
Daniel Veillarded6c5492005-07-23 15:00:22 +00001290 if (ret->xctxt == NULL) {
1291 xmlSchematronPErrMemory(NULL, "allocating schema parser XPath context",
1292 NULL);
1293 xmlSchematronFreeValidCtxt(ret);
1294 return (NULL);
1295 }
1296 for (i = 0;i < schema->nbNamespaces;i++) {
1297 if ((schema->namespaces[2 * i] == NULL) ||
1298 (schema->namespaces[2 * i + 1] == NULL))
1299 break;
1300 xmlXPathRegisterNs(ret->xctxt, schema->namespaces[2 * i + 1],
1301 schema->namespaces[2 * i]);
1302 }
1303 return (ret);
1304}
1305
1306/**
1307 * xmlSchematronFreeValidCtxt:
1308 * @ctxt: the schema validation context
1309 *
1310 * Free the resources associated to the schema validation context
1311 */
1312void
1313xmlSchematronFreeValidCtxt(xmlSchematronValidCtxtPtr ctxt)
1314{
1315 if (ctxt == NULL)
1316 return;
1317 if (ctxt->xctxt != NULL)
1318 xmlXPathFreeContext(ctxt->xctxt);
1319 if (ctxt->dict != NULL)
1320 xmlDictFree(ctxt->dict);
1321 xmlFree(ctxt);
1322}
1323
1324static xmlNodePtr
1325xmlSchematronNextNode(xmlNodePtr cur) {
1326 if (cur->children != NULL) {
1327 /*
1328 * Do not descend on entities declarations
1329 */
1330 if (cur->children->type != XML_ENTITY_DECL) {
1331 cur = cur->children;
1332 /*
1333 * Skip DTDs
1334 */
1335 if (cur->type != XML_DTD_NODE)
1336 return(cur);
1337 }
1338 }
1339
1340 while (cur->next != NULL) {
1341 cur = cur->next;
1342 if ((cur->type != XML_ENTITY_DECL) &&
1343 (cur->type != XML_DTD_NODE))
1344 return(cur);
1345 }
1346
1347 do {
1348 cur = cur->parent;
1349 if (cur == NULL) return(NULL);
1350 if (cur->type == XML_DOCUMENT_NODE) return(NULL);
1351 if (cur->next != NULL) {
1352 cur = cur->next;
1353 return(cur);
1354 }
1355 } while (cur != NULL);
1356 return(cur);
1357}
1358
1359/**
1360 * xmlSchematronRunTest:
1361 * @ctxt: the schema validation context
1362 * @test: the current test
1363 * @instance: the document instace tree
1364 * @cur: the current node in the instance
1365 *
1366 * Validate a rule against a tree instance at a given position
1367 *
1368 * Returns 1 in case of success, 0 if error and -1 in case of internal error
1369 */
1370static int
1371xmlSchematronRunTest(xmlSchematronValidCtxtPtr ctxt,
1372 xmlSchematronTestPtr test, xmlDocPtr instance, xmlNodePtr cur)
1373{
1374 xmlXPathObjectPtr ret;
1375 int failed;
1376
1377 failed = 0;
1378 ctxt->xctxt->doc = instance;
1379 ctxt->xctxt->node = cur;
1380 ret = xmlXPathCompiledEval(test->comp, ctxt->xctxt);
1381 if (ret == NULL) {
1382 failed = 1;
1383 } else switch (ret->type) {
1384 case XPATH_XSLT_TREE:
1385 case XPATH_NODESET:
1386 if ((ret->nodesetval == NULL) ||
1387 (ret->nodesetval->nodeNr == 0))
1388 failed = 1;
1389 break;
1390 case XPATH_BOOLEAN:
1391 failed = !ret->boolval;
1392 break;
1393 case XPATH_NUMBER:
1394 if ((xmlXPathIsNaN(ret->floatval)) ||
1395 (ret->floatval == 0.0))
1396 failed = 1;
1397 break;
1398 case XPATH_STRING:
1399 if ((ret->stringval == NULL) ||
1400 (ret->stringval[0] == 0))
1401 failed = 1;
1402 break;
1403 case XPATH_UNDEFINED:
1404 case XPATH_POINT:
1405 case XPATH_RANGE:
1406 case XPATH_LOCATIONSET:
1407 case XPATH_USERS:
1408 failed = 1;
1409 break;
1410 }
Daniel Veillardd4501d72005-07-24 14:27:16 +00001411 if (failed)
1412 ctxt->nberrors++;
1413 xmlSchematronReportSuccess(ctxt, test, cur, !failed);
Daniel Veillarded6c5492005-07-23 15:00:22 +00001414
1415 return(!failed);
1416}
1417
1418/**
1419 * xmlSchematronValidateDoc:
1420 * @ctxt: the schema validation context
1421 * @instance: the document instace tree
1422 *
1423 * Validate a tree instance against the schematron
1424 *
1425 * Returns 0 in case of success, -1 in case of internal error
1426 * and an error count otherwise.
1427 */
1428int
1429xmlSchematronValidateDoc(xmlSchematronValidCtxtPtr ctxt, xmlDocPtr instance)
1430{
Daniel Veillarde70375c2005-07-30 21:09:12 +00001431 xmlNodePtr cur, root;
1432 xmlSchematronPatternPtr pattern;
Daniel Veillarded6c5492005-07-23 15:00:22 +00001433 xmlSchematronRulePtr rule;
1434 xmlSchematronTestPtr test;
Daniel Veillarde70375c2005-07-30 21:09:12 +00001435 int matched;
Daniel Veillarded6c5492005-07-23 15:00:22 +00001436
1437 if ((ctxt == NULL) || (ctxt->schema == NULL) ||
1438 (ctxt->schema->rules == NULL) || (instance == NULL))
1439 return(-1);
1440 ctxt->nberrors = 0;
Daniel Veillarde70375c2005-07-30 21:09:12 +00001441 root = xmlDocGetRootElement(instance);
1442 if (root == NULL) {
1443 TODO
1444 ctxt->nberrors++;
1445 return(1);
1446 }
1447 if ((ctxt->flags & XML_SCHEMATRON_OUT_QUIET) ||
1448 (ctxt->flags == 0)) {
1449 /*
1450 * we are just trying to assert the validity of the document,
1451 * speed primes over the output, run in a single pass
1452 */
1453 cur = root;
1454 while (cur != NULL) {
1455 rule = ctxt->schema->rules;
1456 while (rule != NULL) {
1457 if (xmlPatternMatch(rule->pattern, cur) == 1) {
1458 test = rule->tests;
1459 while (test != NULL) {
1460 xmlSchematronRunTest(ctxt, test, instance, cur);
1461 test = test->next;
1462 }
Daniel Veillarded6c5492005-07-23 15:00:22 +00001463 }
Daniel Veillarde70375c2005-07-30 21:09:12 +00001464 rule = rule->next;
Daniel Veillarded6c5492005-07-23 15:00:22 +00001465 }
Daniel Veillarde70375c2005-07-30 21:09:12 +00001466
1467 cur = xmlSchematronNextNode(cur);
Daniel Veillarded6c5492005-07-23 15:00:22 +00001468 }
Daniel Veillarde70375c2005-07-30 21:09:12 +00001469 } else {
1470 /*
1471 * Process all contexts one at a time
1472 */
1473 pattern = ctxt->schema->patterns;
1474
1475 while (pattern != NULL) {
1476 matched = 0;
1477 /*
1478 * TODO convert the pattern rule to a direct XPath and
1479 * compute directly instead of using the pattern matching
1480 * over the full document...
1481 * Check the exact semantic
1482 */
1483 cur = root;
1484 while (cur != NULL) {
1485 rule = pattern->rules;
1486 while (rule != NULL) {
1487 if (xmlPatternMatch(rule->pattern, cur) == 1) {
1488 test = rule->tests;
1489 while (test != NULL) {
1490 xmlSchematronRunTest(ctxt, test, instance, cur);
1491 test = test->next;
1492 }
1493 }
1494 rule = rule->patnext;
1495 }
1496
1497 cur = xmlSchematronNextNode(cur);
1498 }
1499 }
Daniel Veillarded6c5492005-07-23 15:00:22 +00001500 }
1501 return(ctxt->nberrors);
1502}
1503
1504#ifdef STANDALONE
1505int
1506main(void)
1507{
1508 int ret;
1509 xmlDocPtr instance;
1510 xmlSchematronParserCtxtPtr pctxt;
1511 xmlSchematronValidCtxtPtr vctxt;
1512 xmlSchematronPtr schema = NULL;
1513
1514 pctxt = xmlSchematronNewParserCtxt("tst.sct");
1515 if (pctxt == NULL) {
1516 fprintf(stderr, "failed to build schematron parser\n");
1517 } else {
1518 schema = xmlSchematronParse(pctxt);
1519 if (schema == NULL) {
1520 fprintf(stderr, "failed to compile schematron\n");
1521 }
1522 xmlSchematronFreeParserCtxt(pctxt);
1523 }
1524 instance = xmlReadFile("tst.sct", NULL,
1525 XML_PARSE_NOENT | XML_PARSE_NOCDATA);
1526 if (instance == NULL) {
1527 fprintf(stderr, "failed to parse instance\n");
1528 }
1529 if ((schema != NULL) && (instance != NULL)) {
1530 vctxt = xmlSchematronNewValidCtxt(schema);
1531 if (vctxt == NULL) {
1532 fprintf(stderr, "failed to build schematron validator\n");
1533 } else {
1534 ret = xmlSchematronValidateDoc(vctxt, instance);
1535 xmlSchematronFreeValidCtxt(vctxt);
1536 }
1537 }
1538 xmlSchematronFree(schema);
1539 xmlFreeDoc(instance);
1540
1541 xmlCleanupParser();
1542 xmlMemoryDump();
1543
1544 return (0);
1545}
1546#endif
1547#endif /* LIBXML_SCHEMATRON_ENABLED */