Mercurial > hg > piper-cpp
comparison ext/sord/src/sord_validate.c @ 226:c5cdc9e6a4bf
Add these external library files
author | Chris Cannam <cannam@all-day-breakfast.com> |
---|---|
date | Fri, 09 Jun 2017 16:41:31 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
225:025b3e2f7c17 | 226:c5cdc9e6a4bf |
---|---|
1 /* | |
2 Copyright 2012-2017 David Robillard <http://drobilla.net> | |
3 | |
4 Permission to use, copy, modify, and/or distribute this software for any | |
5 purpose with or without fee is hereby granted, provided that the above | |
6 copyright notice and this permission notice appear in all copies. | |
7 | |
8 THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
9 WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
10 MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
11 ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
12 WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
13 ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
14 OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
15 */ | |
16 | |
17 #define _BSD_SOURCE 1 // for realpath | |
18 #define _DEFAULT_SOURCE 1 // for realpath | |
19 | |
20 #include <assert.h> | |
21 #include <stdlib.h> | |
22 #include <string.h> | |
23 | |
24 #ifdef _WIN32 | |
25 # include <windows.h> | |
26 #endif | |
27 | |
28 #include "serd/serd.h" | |
29 #include "sord/sord.h" | |
30 #include "sord_config.h" | |
31 | |
32 #ifdef HAVE_PCRE | |
33 # include <pcre.h> | |
34 #endif | |
35 | |
36 #define USTR(s) ((const uint8_t*)s) | |
37 | |
38 #define NS_foaf (const uint8_t*)"http://xmlns.com/foaf/0.1/" | |
39 #define NS_owl (const uint8_t*)"http://www.w3.org/2002/07/owl#" | |
40 #define NS_rdf (const uint8_t*)"http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |
41 #define NS_rdfs (const uint8_t*)"http://www.w3.org/2000/01/rdf-schema#" | |
42 #define NS_xsd (const uint8_t*)"http://www.w3.org/2001/XMLSchema#" | |
43 | |
44 typedef struct { | |
45 SordNode* foaf_Document; | |
46 SordNode* owl_AnnotationProperty; | |
47 SordNode* owl_Class; | |
48 SordNode* owl_DatatypeProperty; | |
49 SordNode* owl_FunctionalProperty; | |
50 SordNode* owl_InverseFunctionalProperty; | |
51 SordNode* owl_ObjectProperty; | |
52 SordNode* owl_OntologyProperty; | |
53 SordNode* owl_Restriction; | |
54 SordNode* owl_Thing; | |
55 SordNode* owl_cardinality; | |
56 SordNode* owl_equivalentClass; | |
57 SordNode* owl_maxCardinality; | |
58 SordNode* owl_minCardinality; | |
59 SordNode* owl_onDatatype; | |
60 SordNode* owl_onProperty; | |
61 SordNode* owl_someValuesFrom; | |
62 SordNode* owl_withRestrictions; | |
63 SordNode* rdf_PlainLiteral; | |
64 SordNode* rdf_Property; | |
65 SordNode* rdf_first; | |
66 SordNode* rdf_rest; | |
67 SordNode* rdf_type; | |
68 SordNode* rdfs_Class; | |
69 SordNode* rdfs_Literal; | |
70 SordNode* rdfs_Resource; | |
71 SordNode* rdfs_domain; | |
72 SordNode* rdfs_label; | |
73 SordNode* rdfs_range; | |
74 SordNode* rdfs_subClassOf; | |
75 SordNode* xsd_anyURI; | |
76 SordNode* xsd_decimal; | |
77 SordNode* xsd_double; | |
78 SordNode* xsd_maxInclusive; | |
79 SordNode* xsd_minInclusive; | |
80 SordNode* xsd_pattern; | |
81 SordNode* xsd_string; | |
82 } URIs; | |
83 | |
84 int n_errors = 0; | |
85 int n_restrictions = 0; | |
86 bool one_line_errors = false; | |
87 | |
88 static int | |
89 print_version(void) | |
90 { | |
91 printf("sord_validate " SORD_VERSION | |
92 " <http://drobilla.net/software/sord>\n"); | |
93 printf("Copyright 2012-2017 David Robillard <http://drobilla.net>.\n" | |
94 "License: <http://www.opensource.org/licenses/isc>\n" | |
95 "This is free software; you are free to change and redistribute it." | |
96 "\nThere is NO WARRANTY, to the extent permitted by law.\n"); | |
97 return 0; | |
98 } | |
99 | |
100 static int | |
101 print_usage(const char* name, bool error) | |
102 { | |
103 FILE* const os = error ? stderr : stdout; | |
104 fprintf(os, "Usage: %s [OPTION]... INPUT...\n", name); | |
105 fprintf(os, "Validate RDF data\n\n"); | |
106 fprintf(os, " -h Display this help and exit\n"); | |
107 fprintf(os, " -l Print errors on a single line.\n"); | |
108 fprintf(os, " -v Display version information and exit\n"); | |
109 fprintf(os, | |
110 "Validate RDF data. This is a simple validator which checks\n" | |
111 "that all used properties are actually defined. It does not do\n" | |
112 "any fancy file retrieval, the files passed on the command line\n" | |
113 "are the only data that is read. In other words, you must pass\n" | |
114 "the definition of all vocabularies used on the command line.\n"); | |
115 return error ? 1 : 0; | |
116 } | |
117 | |
118 static uint8_t* | |
119 absolute_path(const uint8_t* path) | |
120 { | |
121 #ifdef _WIN32 | |
122 char* out = (char*)malloc(MAX_PATH); | |
123 GetFullPathName((const char*)path, MAX_PATH, out, NULL); | |
124 return (uint8_t*)out; | |
125 #else | |
126 return (uint8_t*)realpath((const char*)path, NULL); | |
127 #endif | |
128 } | |
129 | |
130 static int | |
131 errorf(const SordQuad quad, const char* fmt, ...) | |
132 { | |
133 va_list args; | |
134 va_start(args, fmt); | |
135 fprintf(stderr, "error: "); | |
136 vfprintf(stderr, fmt, args); | |
137 va_end(args); | |
138 | |
139 const char* sep = one_line_errors ? "\t" : "\n "; | |
140 fprintf(stderr, "%s%s%s%s%s%s\n", | |
141 sep, (const char*)sord_node_get_string(quad[SORD_SUBJECT]), | |
142 sep, (const char*)sord_node_get_string(quad[SORD_PREDICATE]), | |
143 sep, (const char*)sord_node_get_string(quad[SORD_OBJECT])); | |
144 | |
145 ++n_errors; | |
146 return 1; | |
147 } | |
148 | |
149 static bool | |
150 is_descendant_of(SordModel* model, | |
151 const URIs* uris, | |
152 const SordNode* child, | |
153 const SordNode* parent, | |
154 const SordNode* pred) | |
155 { | |
156 if (!child) { | |
157 return false; | |
158 } else if (sord_node_equals(child, parent) || | |
159 sord_ask(model, child, uris->owl_equivalentClass, parent, NULL)) { | |
160 return true; | |
161 } | |
162 | |
163 SordIter* i = sord_search(model, child, pred, NULL, NULL); | |
164 for (; !sord_iter_end(i); sord_iter_next(i)) { | |
165 const SordNode* o = sord_iter_get_node(i, SORD_OBJECT); | |
166 if (sord_node_equals(child, o)) { | |
167 continue; // Weird class is explicitly a descendent of itself | |
168 } | |
169 if (is_descendant_of(model, uris, o, parent, pred)) { | |
170 sord_iter_free(i); | |
171 return true; | |
172 } | |
173 } | |
174 sord_iter_free(i); | |
175 | |
176 return false; | |
177 } | |
178 | |
179 static bool | |
180 regexp_match(const uint8_t* pat, const char* str) | |
181 { | |
182 #ifdef HAVE_PCRE | |
183 // Append a $ to the pattern so we only match if the entire string matches | |
184 const size_t len = strlen((const char*)pat); | |
185 char* const regx = (char*)malloc(len + 2); | |
186 memcpy(regx, pat, len); | |
187 regx[len] = '$'; | |
188 regx[len + 1] = '\0'; | |
189 | |
190 const char* err; | |
191 int erroffset; | |
192 pcre* re = pcre_compile(regx, PCRE_ANCHORED, &err, &erroffset, NULL); | |
193 free(regx); | |
194 if (!re) { | |
195 fprintf(stderr, "Error in pattern `%s' at offset %d (%s)\n", | |
196 pat, erroffset, err); | |
197 return false; | |
198 } | |
199 | |
200 const bool ret = pcre_exec(re, NULL, str, strlen(str), 0, 0, NULL, 0) >= 0; | |
201 pcre_free(re); | |
202 return ret; | |
203 #endif // HAVE_PCRE | |
204 return true; | |
205 } | |
206 | |
207 static int | |
208 bound_cmp(SordModel* model, | |
209 const URIs* uris, | |
210 const SordNode* literal, | |
211 const SordNode* type, | |
212 const SordNode* bound) | |
213 { | |
214 const char* str = (const char*)sord_node_get_string(literal); | |
215 const char* bound_str = (const char*)sord_node_get_string(bound); | |
216 const bool is_numeric = | |
217 is_descendant_of(model, uris, type, uris->xsd_decimal, uris->owl_onDatatype) || | |
218 is_descendant_of(model, uris, type, uris->xsd_double, uris->owl_onDatatype); | |
219 | |
220 if (is_numeric) { | |
221 const double fbound = serd_strtod(bound_str, NULL); | |
222 const double fliteral = serd_strtod(str, NULL); | |
223 return ((fliteral < fbound) ? -1 : | |
224 (fliteral > fbound) ? 1 : | |
225 0); | |
226 } else { | |
227 return strcmp(str, bound_str); | |
228 } | |
229 } | |
230 | |
231 static bool | |
232 check_restriction(SordModel* model, | |
233 const URIs* uris, | |
234 const SordNode* literal, | |
235 const SordNode* type, | |
236 const SordNode* restriction) | |
237 { | |
238 size_t len = 0; | |
239 const char* str = (const char*)sord_node_get_string_counted(literal, &len); | |
240 | |
241 // Check xsd:pattern | |
242 SordIter* p = sord_search(model, restriction, uris->xsd_pattern, 0, 0); | |
243 if (p) { | |
244 const SordNode* pat = sord_iter_get_node(p, SORD_OBJECT); | |
245 if (!regexp_match(sord_node_get_string(pat), str)) { | |
246 fprintf(stderr, "`%s' does not match <%s> pattern `%s'\n", | |
247 sord_node_get_string(literal), | |
248 sord_node_get_string(type), | |
249 sord_node_get_string(pat)); | |
250 sord_iter_free(p); | |
251 return false; | |
252 } | |
253 sord_iter_free(p); | |
254 ++n_restrictions; | |
255 } | |
256 | |
257 // Check xsd:minInclusive | |
258 SordIter* l = sord_search(model, restriction, uris->xsd_minInclusive, 0, 0); | |
259 if (l) { | |
260 const SordNode* lower = sord_iter_get_node(l, SORD_OBJECT); | |
261 if (bound_cmp(model, uris, literal, type, lower) < 0) { | |
262 fprintf(stderr, "`%s' is not >= <%s> minimum `%s'\n", | |
263 sord_node_get_string(literal), | |
264 sord_node_get_string(type), | |
265 sord_node_get_string(lower)); | |
266 sord_iter_free(l); | |
267 return false; | |
268 } | |
269 sord_iter_free(l); | |
270 ++n_restrictions; | |
271 } | |
272 | |
273 // Check xsd:maxInclusive | |
274 SordIter* u = sord_search(model, restriction, uris->xsd_maxInclusive, 0, 0); | |
275 if (u) { | |
276 const SordNode* upper = sord_iter_get_node(u, SORD_OBJECT); | |
277 if (bound_cmp(model, uris, literal, type, upper) > 0) { | |
278 fprintf(stderr, "`%s' is not <= <%s> maximum `%s'\n", | |
279 sord_node_get_string(literal), | |
280 sord_node_get_string(type), | |
281 sord_node_get_string(upper)); | |
282 sord_iter_free(u); | |
283 return false; | |
284 } | |
285 sord_iter_free(u); | |
286 ++n_restrictions; | |
287 } | |
288 | |
289 return true; // Unknown restriction, be quietly tolerant | |
290 } | |
291 | |
292 static bool | |
293 literal_is_valid(SordModel* model, | |
294 const URIs* uris, | |
295 const SordQuad quad, | |
296 const SordNode* literal, | |
297 const SordNode* type) | |
298 { | |
299 if (!type) { | |
300 return true; | |
301 } | |
302 | |
303 /* Check that literal data is related to required type. We don't do a | |
304 strict subtype check here because e.g. an xsd:decimal might be a valid | |
305 xsd:unsignedInt, which the pattern checks will verify, but if the | |
306 literal type is not related to the required type at all | |
307 (e.g. xsd:decimal and xsd:string) there is a problem. */ | |
308 const SordNode* datatype = sord_node_get_datatype(literal); | |
309 if (datatype && datatype != type) { | |
310 if (!is_descendant_of( | |
311 model, uris, | |
312 datatype, type, uris->owl_onDatatype) && | |
313 !is_descendant_of( | |
314 model, uris, | |
315 type, datatype, uris->owl_onDatatype) && | |
316 !(sord_node_equals(datatype, uris->xsd_decimal) && | |
317 is_descendant_of( | |
318 model, uris, | |
319 type, uris->xsd_double, uris->owl_onDatatype))) { | |
320 errorf(quad, | |
321 "Literal `%s' datatype <%s> is not compatible with <%s>\n", | |
322 sord_node_get_string(literal), | |
323 sord_node_get_string(datatype), | |
324 sord_node_get_string(type)); | |
325 return false; | |
326 } | |
327 } | |
328 | |
329 // Find restrictions list | |
330 SordIter* rs = sord_search(model, type, uris->owl_withRestrictions, 0, 0); | |
331 if (sord_iter_end(rs)) { | |
332 return true; // No restrictions | |
333 } | |
334 | |
335 // Walk list, checking each restriction | |
336 const SordNode* head = sord_iter_get_node(rs, SORD_OBJECT); | |
337 while (head) { | |
338 SordIter* f = sord_search(model, head, uris->rdf_first, 0, 0); | |
339 if (!f) { | |
340 break; // Reached end of restrictions list without failure | |
341 } | |
342 | |
343 // Check this restriction | |
344 const bool good = check_restriction( | |
345 model, uris, literal, type, sord_iter_get_node(f, SORD_OBJECT)); | |
346 sord_iter_free(f); | |
347 | |
348 if (!good) { | |
349 sord_iter_free(rs); | |
350 return false; // Failed, literal is invalid | |
351 } | |
352 | |
353 // Seek to next list node | |
354 SordIter* n = sord_search(model, head, uris->rdf_rest, 0, 0); | |
355 head = n ? sord_iter_get_node(n, SORD_OBJECT) : NULL; | |
356 sord_iter_free(n); | |
357 } | |
358 | |
359 sord_iter_free(rs); | |
360 | |
361 SordIter* s = sord_search(model, type, uris->owl_onDatatype, 0, 0); | |
362 if (s) { | |
363 const SordNode* super = sord_iter_get_node(s, SORD_OBJECT); | |
364 const bool good = literal_is_valid( | |
365 model, uris, quad, literal, super); | |
366 sord_iter_free(s); | |
367 return good; // Match iff literal also matches supertype | |
368 } | |
369 | |
370 return true; // Matches top level type | |
371 } | |
372 | |
373 static bool | |
374 check_type(SordModel* model, | |
375 const URIs* uris, | |
376 const SordQuad quad, | |
377 const SordNode* node, | |
378 const SordNode* type) | |
379 { | |
380 if (sord_node_equals(type, uris->rdfs_Resource) || | |
381 sord_node_equals(type, uris->owl_Thing)) { | |
382 return true; | |
383 } | |
384 | |
385 if (sord_node_get_type(node) == SORD_LITERAL) { | |
386 if (sord_node_equals(type, uris->rdfs_Literal)) { | |
387 return true; | |
388 } else if (sord_node_equals(type, uris->rdf_PlainLiteral)) { | |
389 return !sord_node_get_language(node); | |
390 } else { | |
391 return literal_is_valid(model, uris, quad, node, type); | |
392 } | |
393 } else if (sord_node_get_type(node) == SORD_URI) { | |
394 if (sord_node_equals(type, uris->foaf_Document)) { | |
395 return true; // Questionable... | |
396 } else if (is_descendant_of( | |
397 model, uris, | |
398 type, uris->xsd_anyURI, uris->owl_onDatatype)) { | |
399 /* Type is any URI and this is a URI, so pass. Restrictions on | |
400 anyURI subtypes are not currently checked (very uncommon). */ | |
401 return true; // Type is anyURI, and this is a URI | |
402 } else { | |
403 SordIter* t = sord_search(model, node, uris->rdf_type, NULL, NULL); | |
404 for (; !sord_iter_end(t); sord_iter_next(t)) { | |
405 if (is_descendant_of(model, uris, | |
406 sord_iter_get_node(t, SORD_OBJECT), | |
407 type, | |
408 uris->rdfs_subClassOf)) { | |
409 sord_iter_free(t); | |
410 return true; | |
411 } | |
412 } | |
413 sord_iter_free(t); | |
414 return false; | |
415 } | |
416 } else { | |
417 return true; // Blanks often lack explicit types, ignore | |
418 } | |
419 | |
420 return false; | |
421 } | |
422 | |
423 static uint64_t | |
424 count_non_blanks(SordIter* i, SordQuadIndex field) | |
425 { | |
426 uint64_t n = 0; | |
427 for (; !sord_iter_end(i); sord_iter_next(i)) { | |
428 const SordNode* node = sord_iter_get_node(i, field); | |
429 if (sord_node_get_type(node) != SORD_BLANK) { | |
430 ++n; | |
431 } | |
432 } | |
433 return n; | |
434 } | |
435 | |
436 static int | |
437 check_properties(SordModel* model, URIs* uris) | |
438 { | |
439 int st = 0; | |
440 SordIter* i = sord_begin(model); | |
441 for (; !sord_iter_end(i); sord_iter_next(i)) { | |
442 SordQuad quad; | |
443 sord_iter_get(i, quad); | |
444 | |
445 const SordNode* subj = quad[SORD_SUBJECT]; | |
446 const SordNode* pred = quad[SORD_PREDICATE]; | |
447 const SordNode* obj = quad[SORD_OBJECT]; | |
448 | |
449 bool is_any_property = false; | |
450 SordIter* t = sord_search(model, pred, uris->rdf_type, NULL, NULL); | |
451 for (; !sord_iter_end(t); sord_iter_next(t)) { | |
452 if (is_descendant_of(model, uris, | |
453 sord_iter_get_node(t, SORD_OBJECT), | |
454 uris->rdf_Property, | |
455 uris->rdfs_subClassOf)) { | |
456 is_any_property = true; | |
457 break; | |
458 } | |
459 } | |
460 sord_iter_free(t); | |
461 | |
462 const bool is_ObjectProperty = sord_ask( | |
463 model, pred, uris->rdf_type, uris->owl_ObjectProperty, 0); | |
464 const bool is_FunctionalProperty = sord_ask( | |
465 model, pred, uris->rdf_type, uris->owl_FunctionalProperty, 0); | |
466 const bool is_InverseFunctionalProperty = sord_ask( | |
467 model, pred, uris->rdf_type, uris->owl_InverseFunctionalProperty, 0); | |
468 const bool is_DatatypeProperty = sord_ask( | |
469 model, pred, uris->rdf_type, uris->owl_DatatypeProperty, 0); | |
470 | |
471 if (!is_any_property) { | |
472 st = errorf(quad, "Use of undefined property"); | |
473 } | |
474 | |
475 if (!sord_ask(model, pred, uris->rdfs_label, NULL, NULL)) { | |
476 st = errorf(quad, "Property <%s> has no label", | |
477 sord_node_get_string(pred)); | |
478 } | |
479 | |
480 if (is_DatatypeProperty && | |
481 sord_node_get_type(obj) != SORD_LITERAL) { | |
482 st = errorf(quad, "Datatype property with non-literal value"); | |
483 } | |
484 | |
485 if (is_ObjectProperty && | |
486 sord_node_get_type(obj) == SORD_LITERAL) { | |
487 st = errorf(quad, "Object property with literal value"); | |
488 } | |
489 | |
490 if (is_FunctionalProperty) { | |
491 SordIter* o = sord_search(model, subj, pred, NULL, NULL); | |
492 const uint64_t n = count_non_blanks(o, SORD_OBJECT); | |
493 if (n > 1) { | |
494 st = errorf(quad, "Functional property with %u objects", n); | |
495 } | |
496 sord_iter_free(o); | |
497 } | |
498 | |
499 if (is_InverseFunctionalProperty) { | |
500 SordIter* s = sord_search(model, NULL, pred, obj, NULL); | |
501 const unsigned n = count_non_blanks(s, SORD_SUBJECT); | |
502 if (n > 1) { | |
503 st = errorf( | |
504 quad, "Inverse functional property with %u subjects", n); | |
505 } | |
506 sord_iter_free(s); | |
507 } | |
508 | |
509 if (sord_node_equals(pred, uris->rdf_type) && | |
510 !sord_ask(model, obj, uris->rdf_type, uris->rdfs_Class, NULL) && | |
511 !sord_ask(model, obj, uris->rdf_type, uris->owl_Class, NULL)) { | |
512 st = errorf(quad, "Type is not a rdfs:Class or owl:Class"); | |
513 } | |
514 | |
515 if (sord_node_get_type(obj) == SORD_LITERAL && | |
516 !literal_is_valid(model, uris, quad, | |
517 obj, sord_node_get_datatype(obj))) { | |
518 st = errorf(quad, "Literal does not match datatype"); | |
519 } | |
520 | |
521 SordIter* r = sord_search(model, pred, uris->rdfs_range, NULL, NULL); | |
522 for (; !sord_iter_end(r); sord_iter_next(r)) { | |
523 const SordNode* range = sord_iter_get_node(r, SORD_OBJECT); | |
524 if (!check_type(model, uris, quad, obj, range)) { | |
525 st = errorf(quad, "Object not in range <%s>\n", | |
526 sord_node_get_string(range)); | |
527 } | |
528 } | |
529 sord_iter_free(r); | |
530 | |
531 SordIter* d = sord_search(model, pred, uris->rdfs_domain, NULL, NULL); | |
532 if (d) { | |
533 const SordNode* domain = sord_iter_get_node(d, SORD_OBJECT); | |
534 if (!check_type(model, uris, quad, subj, domain)) { | |
535 st = errorf(quad, "Subject not in domain <%s>", | |
536 sord_node_get_string(domain)); | |
537 } | |
538 sord_iter_free(d); | |
539 } | |
540 } | |
541 sord_iter_free(i); | |
542 | |
543 return st; | |
544 } | |
545 | |
546 static int | |
547 check_instance(SordModel* model, | |
548 const URIs* uris, | |
549 const SordNode* restriction, | |
550 const SordQuad quad) | |
551 { | |
552 const SordNode* instance = quad[SORD_SUBJECT]; | |
553 int st = 0; | |
554 | |
555 const SordNode* prop = sord_get( | |
556 model, restriction, uris->owl_onProperty, NULL, NULL); | |
557 if (!prop) { | |
558 return 0; | |
559 } | |
560 | |
561 const unsigned values = sord_count(model, instance, prop, NULL, NULL); | |
562 | |
563 // Check exact cardinality | |
564 const SordNode* card = sord_get( | |
565 model, restriction, uris->owl_cardinality, NULL, NULL); | |
566 if (card) { | |
567 const unsigned c = atoi((const char*)sord_node_get_string(card)); | |
568 if (values != c) { | |
569 st = errorf(quad, "Property %s on %s has %u != %u values", | |
570 sord_node_get_string(prop), | |
571 sord_node_get_string(instance), | |
572 values, c); | |
573 } | |
574 } | |
575 | |
576 // Check minimum cardinality | |
577 const SordNode* minCard = sord_get( | |
578 model, restriction, uris->owl_minCardinality, NULL, NULL); | |
579 if (minCard) { | |
580 const unsigned m = atoi((const char*)sord_node_get_string(minCard)); | |
581 if (values < m) { | |
582 st = errorf(quad, "Property %s on %s has %u < %u values", | |
583 sord_node_get_string(prop), | |
584 sord_node_get_string(instance), | |
585 values, m); | |
586 } | |
587 } | |
588 | |
589 // Check maximum cardinality | |
590 const SordNode* maxCard = sord_get( | |
591 model, restriction, uris->owl_maxCardinality, NULL, NULL); | |
592 if (maxCard) { | |
593 const unsigned m = atoi((const char*)sord_node_get_string(maxCard)); | |
594 if (values < m) { | |
595 st = errorf(quad, "Property %s on %s has %u > %u values", | |
596 sord_node_get_string(prop), | |
597 sord_node_get_string(instance), | |
598 values, m); | |
599 } | |
600 } | |
601 | |
602 // Check someValuesFrom | |
603 SordIter* sf = sord_search( | |
604 model, restriction, uris->owl_someValuesFrom, NULL, NULL); | |
605 if (sf) { | |
606 const SordNode* type = sord_iter_get_node(sf, SORD_OBJECT); | |
607 | |
608 SordIter* v = sord_search(model, instance, prop, NULL, NULL); | |
609 bool found = false; | |
610 for (; !sord_iter_end(v); sord_iter_next(v)) { | |
611 const SordNode* value = sord_iter_get_node(v, SORD_OBJECT); | |
612 if (check_type(model, uris, quad, value, type)) { | |
613 found = true; | |
614 break; | |
615 } | |
616 } | |
617 if (!found) { | |
618 st = errorf(quad, "%s has no <%s> values of type <%s>\n", | |
619 sord_node_get_string(instance), | |
620 sord_node_get_string(prop), | |
621 sord_node_get_string(type)); | |
622 } | |
623 sord_iter_free(v); | |
624 } | |
625 sord_iter_free(sf); | |
626 | |
627 return st; | |
628 } | |
629 | |
630 static int | |
631 check_class_instances(SordModel* model, | |
632 const URIs* uris, | |
633 const SordNode* restriction, | |
634 const SordNode* klass) | |
635 { | |
636 // Check immediate instances of this class | |
637 SordIter* i = sord_search(model, NULL, uris->rdf_type, klass, NULL); | |
638 for (; !sord_iter_end(i); sord_iter_next(i)) { | |
639 SordQuad quad; | |
640 sord_iter_get(i, quad); | |
641 check_instance(model, uris, restriction, quad); | |
642 } | |
643 sord_iter_free(i); | |
644 | |
645 // Check instances of all subclasses recursively | |
646 SordIter* s = sord_search(model, NULL, uris->rdfs_subClassOf, klass, NULL); | |
647 for (; !sord_iter_end(s); sord_iter_next(s)) { | |
648 const SordNode* subklass = sord_iter_get_node(s, SORD_SUBJECT); | |
649 check_class_instances(model, uris, restriction, subklass); | |
650 } | |
651 sord_iter_free(s); | |
652 | |
653 return 0; | |
654 } | |
655 | |
656 static int | |
657 check_instances(SordModel* model, const URIs* uris) | |
658 { | |
659 int st = 0; | |
660 SordIter* r = sord_search( | |
661 model, NULL, uris->rdf_type, uris->owl_Restriction, NULL); | |
662 for (; !sord_iter_end(r); sord_iter_next(r)) { | |
663 const SordNode* restriction = sord_iter_get_node(r, SORD_SUBJECT); | |
664 const SordNode* prop = sord_get( | |
665 model, restriction, uris->owl_onProperty, NULL, NULL); | |
666 if (!prop) { | |
667 continue; | |
668 } | |
669 | |
670 SordIter* c = sord_search( | |
671 model, NULL, uris->rdfs_subClassOf, restriction, NULL); | |
672 for (; !sord_iter_end(c); sord_iter_next(c)) { | |
673 const SordNode* klass = sord_iter_get_node(c, SORD_SUBJECT); | |
674 check_class_instances(model, uris, restriction, klass); | |
675 } | |
676 sord_iter_free(c); | |
677 } | |
678 sord_iter_free(r); | |
679 | |
680 return st; | |
681 } | |
682 | |
683 int | |
684 main(int argc, char** argv) | |
685 { | |
686 if (argc < 2) { | |
687 return print_usage(argv[0], true); | |
688 } | |
689 | |
690 int a = 1; | |
691 for (; a < argc && argv[a][0] == '-'; ++a) { | |
692 if (argv[a][1] == 'l') { | |
693 one_line_errors = true; | |
694 } else if (argv[a][1] == 'v') { | |
695 return print_version(); | |
696 } else { | |
697 fprintf(stderr, "%s: Unknown option `%s'\n", argv[0], argv[a]); | |
698 return print_usage(argv[0], true); | |
699 } | |
700 } | |
701 | |
702 SordWorld* world = sord_world_new(); | |
703 SordModel* model = sord_new(world, SORD_SPO|SORD_OPS, false); | |
704 SerdEnv* env = serd_env_new(&SERD_NODE_NULL); | |
705 SerdReader* reader = sord_new_reader(model, env, SERD_TURTLE, NULL); | |
706 | |
707 for (; a < argc; ++a) { | |
708 const uint8_t* input = (const uint8_t*)argv[a]; | |
709 uint8_t* in_path = absolute_path(serd_uri_to_path(input)); | |
710 | |
711 if (!in_path) { | |
712 fprintf(stderr, "Skipping file %s\n", input); | |
713 continue; | |
714 } | |
715 | |
716 SerdURI base_uri; | |
717 SerdNode base_uri_node = serd_node_new_file_uri( | |
718 in_path, NULL, &base_uri, true); | |
719 | |
720 serd_env_set_base_uri(env, &base_uri_node); | |
721 const SerdStatus st = serd_reader_read_file(reader, in_path); | |
722 if (st) { | |
723 fprintf(stderr, "error reading %s: %s\n", | |
724 in_path, serd_strerror(st)); | |
725 } | |
726 | |
727 serd_node_free(&base_uri_node); | |
728 free(in_path); | |
729 } | |
730 serd_reader_free(reader); | |
731 serd_env_free(env); | |
732 | |
733 #define URI(prefix, suffix) \ | |
734 uris.prefix##_##suffix = sord_new_uri(world, NS_##prefix #suffix) | |
735 | |
736 URIs uris; | |
737 URI(foaf, Document); | |
738 URI(owl, AnnotationProperty); | |
739 URI(owl, Class); | |
740 URI(owl, DatatypeProperty); | |
741 URI(owl, FunctionalProperty); | |
742 URI(owl, InverseFunctionalProperty); | |
743 URI(owl, ObjectProperty); | |
744 URI(owl, OntologyProperty); | |
745 URI(owl, Restriction); | |
746 URI(owl, Thing); | |
747 URI(owl, cardinality); | |
748 URI(owl, equivalentClass); | |
749 URI(owl, maxCardinality); | |
750 URI(owl, minCardinality); | |
751 URI(owl, onDatatype); | |
752 URI(owl, onProperty); | |
753 URI(owl, someValuesFrom); | |
754 URI(owl, withRestrictions); | |
755 URI(rdf, PlainLiteral); | |
756 URI(rdf, Property); | |
757 URI(rdf, first); | |
758 URI(rdf, rest); | |
759 URI(rdf, type); | |
760 URI(rdfs, Class); | |
761 URI(rdfs, Literal); | |
762 URI(rdfs, Resource); | |
763 URI(rdfs, domain); | |
764 URI(rdfs, label); | |
765 URI(rdfs, range); | |
766 URI(rdfs, subClassOf); | |
767 URI(xsd, anyURI); | |
768 URI(xsd, decimal); | |
769 URI(xsd, double); | |
770 URI(xsd, maxInclusive); | |
771 URI(xsd, minInclusive); | |
772 URI(xsd, pattern); | |
773 URI(xsd, string); | |
774 | |
775 #ifndef HAVE_PCRE | |
776 fprintf(stderr, "warning: Built without PCRE, datatypes not checked.\n"); | |
777 #endif | |
778 | |
779 const int prop_st = check_properties(model, &uris); | |
780 const int inst_st = check_instances(model, &uris); | |
781 | |
782 printf("Found %d errors among %d files (checked %d restrictions)\n", | |
783 n_errors, argc - 1, n_restrictions); | |
784 | |
785 sord_free(model); | |
786 sord_world_free(world); | |
787 return prop_st || inst_st; | |
788 } |