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