diff options
author | David Robillard <d@drobilla.net> | 2018-05-27 15:48:25 +0200 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2018-11-25 22:12:48 +0100 |
commit | 535bc536f0541792b0bab84041e7d82eaa457368 (patch) | |
tree | 581d80e5c49a4275b7bd04b2d87559b4646f2337 | |
parent | 584e7d39adfd3abdb25c2cf9251aa1ea5f293e8e (diff) | |
download | serd-535bc536f0541792b0bab84041e7d82eaa457368.tar.gz serd-535bc536f0541792b0bab84041e7d82eaa457368.tar.bz2 serd-535bc536f0541792b0bab84041e7d82eaa457368.zip |
WIP: Add serd_validate
-rw-r--r-- | src/serd_validate.c | 986 | ||||
-rw-r--r-- | wscript | 43 |
2 files changed, 1017 insertions, 12 deletions
diff --git a/src/serd_validate.c b/src/serd_validate.c new file mode 100644 index 00000000..b852a3fa --- /dev/null +++ b/src/serd_validate.c @@ -0,0 +1,986 @@ +/* + Copyright 2012-2018 David Robillard <http://drobilla.net> + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ + +#define _BSD_SOURCE 1 // for realpath +#define _DEFAULT_SOURCE 1 // for realpath + +#include "serd_config.h" + +#include "serd/serd.h" + +#include <assert.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#ifdef _WIN32 +#include <windows.h> +#endif + +#ifdef HAVE_PCRE +#include <pcre.h> +#endif + +#define NS_foaf "http://xmlns.com/foaf/0.1/" +#define NS_owl "http://www.w3.org/2002/07/owl#" +#define NS_rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define NS_rdfs "http://www.w3.org/2000/01/rdf-schema#" +#define NS_xsd "http://www.w3.org/2001/XMLSchema#" + +#define CERRORF(fmt, ...) fprintf(stderr, "serd_validate: " fmt, __VA_ARGS__); + +#define VERRORF(statement, fmt, ...) \ + report(statement, "error", fmt, __VA_ARGS__); + +#define VERROR(statement, fmt) report(statement, "error", fmt); + +#define VNOTEF(statement, fmt, ...) report(statement, "note", fmt, __VA_ARGS__); + +#define VNOTE(statement, fmt) report(statement, "note", fmt); + +#define SERD_FOREACH(name, range) \ + for (const SerdStatement* name = NULL; \ + !serd_range_empty(range) && (name = serd_range_front(range)); \ + serd_range_next(range)) + +typedef struct +{ + SerdNode* foaf_Document; + SerdNode* owl_Class; + SerdNode* owl_DatatypeProperty; + SerdNode* owl_FunctionalProperty; + SerdNode* owl_InverseFunctionalProperty; + SerdNode* owl_ObjectProperty; + SerdNode* owl_Restriction; + SerdNode* owl_Thing; + SerdNode* owl_allValuesFrom; + SerdNode* owl_cardinality; + SerdNode* owl_equivalentClass; + SerdNode* owl_maxCardinality; + SerdNode* owl_minCardinality; + SerdNode* owl_onDatatype; + SerdNode* owl_onProperty; + SerdNode* owl_someValuesFrom; + SerdNode* owl_withRestrictions; + SerdNode* rdf_PlainLiteral; + SerdNode* rdf_Property; + SerdNode* rdf_first; + SerdNode* rdf_rest; + SerdNode* rdf_type; + SerdNode* rdfs_Class; + SerdNode* rdfs_Datatype; + SerdNode* rdfs_Literal; + SerdNode* rdfs_Resource; + SerdNode* rdfs_domain; + SerdNode* rdfs_label; + SerdNode* rdfs_range; + SerdNode* rdfs_subClassOf; + SerdNode* xsd_anyURI; + SerdNode* xsd_float; + SerdNode* xsd_decimal; + SerdNode* xsd_double; + SerdNode* xsd_maxExclusive; + SerdNode* xsd_maxInclusive; + SerdNode* xsd_minExclusive; + SerdNode* xsd_minInclusive; + SerdNode* xsd_pattern; + SerdNode* xsd_string; +} URIs; + +static int n_errors = 0; +static int n_restrictions = 0; + +static int +check_class_restriction(SerdModel* model, + const URIs* uris, + const SerdNode* restriction, + const SerdStatement* statement, + const SerdNode* instance); + +static int +print_version(void) +{ + printf("serd_validate " SERD_VERSION + " <http://drobilla.net/software/serd>\n"); + printf("Copyright 2012-2018 David Robillard <http://drobilla.net>.\n" + "License: <http://www.opensource.org/licenses/isc>\n" + "This is free software; you are free to change and redistribute it." + "\nThere is NO WARRANTY, to the extent permitted by law.\n"); + return 0; +} + +static int +print_usage(const char* name, bool error) +{ + FILE* const os = error ? stderr : stdout; + fprintf(os, "Usage: %s [OPTION]... INPUT...\n", name); + fprintf(os, "Validate RDF data\n\n"); + fprintf(os, " -h Display this help and exit\n"); + fprintf(os, " -l Print errors on a single line.\n"); + fprintf(os, " -v Display version information and exit\n"); + fprintf(os, + "Validate RDF data. This is a simple validator which checks\n" + "that all used properties are actually defined. It does not do\n" + "any fancy file retrieval, the files passed on the command line\n" + "are the only data that is read. In other words, you must pass\n" + "the definition of all vocabularies used on the command line.\n"); + return error ? 1 : 0; +} + +static char* +absolute_path(const char* path) +{ +#ifdef _WIN32 + char* out = (char*)malloc(MAX_PATH); + GetFullPathName(path, MAX_PATH, out, NULL); + return out; +#else + return realpath(path, NULL); +#endif +} + +static int +report(const SerdStatement* statement, const char* type, const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + + fprintf(stderr, "%s: ", type); + + const SerdCursor* cursor = serd_statement_get_cursor(statement); + if (cursor) { + fprintf(stderr, + "%s:%u:%u: ", + serd_node_get_string(serd_cursor_get_name(cursor)), + serd_cursor_get_line(cursor), + serd_cursor_get_column(cursor)); + } + + vfprintf(stderr, fmt, args); + va_end(args); + + ++n_errors; + return 1; +} + +static bool +check(const bool value) +{ + ++n_restrictions; + return value; +} + +/** Return true iff `child` is a descendant of `parent` by `pred` arcs. + * + * This is, returns true if there is a path from `child` to `parent` by + * following `pred` arcs starting from child. + */ +static bool +is_descendant(SerdModel* model, + const URIs* uris, + const SerdNode* child, + const SerdNode* parent, + const SerdNode* pred) +{ + if (serd_node_equals(child, parent) || + serd_model_ask(model, child, uris->owl_equivalentClass, parent, NULL)) { + return true; + } + + SerdRange* i = serd_model_range(model, child, pred, NULL, NULL); + SERD_FOREACH (s, i) { + const SerdNode* o = serd_statement_get_object(s); + if (serd_node_equals(child, o)) { + continue; // Weird class is explicitly a descendent of itself + } + if (is_descendant(model, uris, o, parent, pred)) { + serd_range_free(i); + return true; + } + } + serd_range_free(i); + + return false; +} + +/** Return true iff `child` is a subclass of `parent`. */ +static bool +is_subclass(SerdModel* model, + const URIs* uris, + const SerdNode* child, + const SerdNode* parent) +{ + return is_descendant(model, uris, child, parent, uris->rdfs_subClassOf); +} + +/** Return true iff `child` is a sub-datatype of `parent`. */ +static bool +is_subdatatype(SerdModel* model, + const URIs* uris, + const SerdNode* child, + const SerdNode* parent) +{ + return is_descendant(model, uris, child, parent, uris->owl_onDatatype); +} + +static bool +regexp_match(const SerdStatement* pat_statement, const char* pat, const char* str) +{ +#ifdef HAVE_PCRE + // Append a $ to the pattern so we only match if the entire string matches + const size_t len = strlen(pat); + char* const regx = (char*)malloc(len + 2); + memcpy(regx, pat, len); + regx[len] = '$'; + regx[len + 1] = '\0'; + + const char* err; + int erroffset; + pcre* re = pcre_compile(regx, PCRE_ANCHORED, &err, &erroffset, NULL); + free(regx); + if (!re) { + VERRORF(pat_statement, + "Error in pattern \"%s\" at offset %d (%s)\n", + pat, + erroffset, + err); + return false; + } + + const bool ret = pcre_exec(re, NULL, str, strlen(str), 0, 0, NULL, 0) >= 0; + pcre_free(re); + return ret; +#endif // HAVE_PCRE + return true; +} + +static int +bound_cmp(SerdModel* model, + const URIs* uris, + const SerdNode* literal, + const SerdNode* type, + const SerdNode* bound) +{ + const char* str = serd_node_get_string(literal); + const char* bound_str = serd_node_get_string(bound); + const bool is_numeric = + is_subdatatype(model, uris, type, uris->xsd_decimal) || + is_subdatatype(model, uris, type, uris->xsd_double); + + if (is_numeric) { + const double fbound = serd_strtod(bound_str, NULL); + const double fliteral = serd_strtod(str, NULL); + return ((fliteral < fbound) ? -1 : (fliteral > fbound) ? 1 : 0); + } else { + return strcmp(str, bound_str); + } +} + +static bool +check_literal_restriction(SerdModel* model, + const URIs* uris, + const SerdStatement* statement, + const SerdNode* literal, + const SerdNode* type, + const SerdNode* restriction) +{ + const char* str = serd_node_get_string(literal); + + // Check xsd:pattern + const SerdStatement* pat_statement = serd_model_get_statement( + model, restriction, uris->xsd_pattern, 0, 0); + if (pat_statement) { + const SerdNode* pat_node = serd_statement_get_object(pat_statement); + const char* pat = serd_node_get_string(pat_node); + if (check(!regexp_match(pat_statement, pat, str))) { + VERRORF(statement, + "Value \"%s\" does not match pattern \"%s\"\n", + serd_node_get_string(literal), + pat); + return false; + } + } + + // Check xsd:minInclusive + const SerdNode* lower = + serd_model_get(model, restriction, uris->xsd_minInclusive, 0, 0); + if (lower) { + if (check(bound_cmp(model, uris, literal, type, lower) < 0)) { + VERRORF(statement, + "Value \"%s\" < minimum \"%s\"\n", + serd_node_get_string(literal), + serd_node_get_string(lower)); + return false; + } + } + + // Check xsd:maxInclusive + const SerdNode* upper = + serd_model_get(model, restriction, uris->xsd_maxInclusive, 0, 0); + if (upper) { + if (check(bound_cmp(model, uris, literal, type, upper) > 0)) { + VERRORF(statement, + "Value \"%s\" > than maximum \"%s\"\n", + serd_node_get_string(literal), + serd_node_get_string(upper)); + return false; + } + } + + // Check xsd:minExclusive + const SerdNode* elower = + serd_model_get(model, restriction, uris->xsd_minExclusive, 0, 0); + if (elower) { + if (check(bound_cmp(model, uris, literal, type, elower) <= 0)) { + VERRORF(statement, + "Value \"%s\" <= exclusive minimum \"%s\"\n", + serd_node_get_string(literal), + serd_node_get_string(elower)); + return false; + } + } + + // Check xsd:maxExclusive + const SerdNode* eupper = + serd_model_get(model, restriction, uris->xsd_maxExclusive, 0, 0); + if (eupper) { + if (check(bound_cmp(model, uris, literal, type, eupper) >= 0)) { + VERRORF(statement, + "Value \"%s\" >= exclusive maximum \"%s\"\n", + serd_node_get_string(literal), + serd_node_get_string(eupper)); + return false; + } + ++n_restrictions; + } + + return true; // Unknown restriction, be quietly tolerant +} + +static bool +is_datatype(SerdModel* model, const URIs* uris, const SerdNode* dtype) +{ + SerdRange* t = serd_model_range(model, dtype, uris->rdf_type, NULL, NULL); + SERD_FOREACH (s, t) { + const SerdNode* type = serd_statement_get_object(s); + if (is_subdatatype(model, uris, type, uris->rdfs_Datatype)) { + return true; // Subdatatype of rdfs:Datatype + } + } + + return false; +} + +static bool +literal_is_valid(SerdModel* model, + const URIs* uris, + const SerdStatement* statement, + const SerdNode* literal, + const SerdNode* type) +{ + if (!type) { + return true; + } + + // Check that datatype is defined + const SerdNode* datatype = serd_node_get_datatype(literal); + if (datatype && !is_datatype(model, uris, datatype)) { + VERRORF(statement, + "Datatype <%s> is not defined\n", + serd_node_get_string(datatype)); + return false; + } + + // Find restrictions list + const SerdNode* head = + serd_model_get(model, type, uris->owl_withRestrictions, 0, 0); + + // Walk list, checking each restriction + while (head) { + SerdIter* f = serd_model_find(model, head, uris->rdf_first, 0, 0); + if (!f) { + break; + } + + const SerdNode* first = serd_statement_get_object(serd_iter_get(f)); + + // Check this restriction + if (!check_literal_restriction( + model, uris, statement, literal, type, first)) { + VNOTEF(serd_iter_get(f), + "Restriction on <%s>\n", + serd_node_get_string(type)); + return false; + } + + // Seek to next list node + head = serd_model_get(model, head, uris->rdf_rest, 0, 0); + } + + // Recurse up datatype hierarchy + const SerdNode* super = + serd_model_get(model, type, uris->owl_onDatatype, 0, 0); + return super ? literal_is_valid(model, uris, statement, literal, super) + : true; +} + +static bool +has_explicit_type(SerdModel* model, + const URIs* uris, + const SerdNode* node, + const SerdNode* klass) +{ + if (serd_model_ask(model, node, uris->rdf_type, klass, NULL)) { + return true; // Directly stated to be an instance + } + + SerdRange* t = serd_model_range(model, node, uris->rdf_type, NULL, NULL); + SERD_FOREACH (s, t) { + if (is_subclass(model, uris, serd_statement_get_object(s), klass)) { + return true; // Explicit instance of a subclass + } + } + + return false; +} + +static bool +is_instance_of(SerdModel* model, + const URIs* uris, + const SerdStatement* statement, + const SerdNode* node, + const SerdNode* klass) +{ + if (!serd_model_ask(model, node, NULL, NULL, NULL)) { + /* Nothing about this node known in the model at all, assume it is some + external resource we can't validate. */ + return true; + } else if (serd_node_get_type(node) == SERD_BLANK) { + /* Be permissive for blank nodes and don't require explicit type + annotation, to avoid countless errors with things like lists. */ + return true; + } + + return (has_explicit_type(model, uris, node, klass) || + serd_node_equals(klass, uris->rdfs_Resource) || + serd_node_equals(klass, uris->owl_Thing)); +} + +static bool +check_instance_type(SerdModel* model, + const URIs* uris, + const SerdStatement* statement, + const SerdNode* node, + const SerdNode* klass) +{ + if (is_subclass(model, uris, klass, uris->rdfs_Literal) || + serd_model_ask(model, klass, uris->rdf_type, uris->rdfs_Datatype, 0)) { + VERROR(statement, "Class instance found where literal expected\n"); + return false; + } + + if (serd_model_ask( + model, klass, uris->rdf_type, uris->owl_Restriction, NULL)) { + if (check_class_restriction(model, uris, klass, statement, node)) { + return false; + } + } + + SerdRange* r = + serd_model_range(model, klass, uris->rdfs_subClassOf, NULL, NULL); + SERD_FOREACH (s, r) { + const SerdNode* super = serd_statement_get_object(s); + if (!serd_node_equals(super, klass)) { + if (!check_instance_type(model, uris, statement, node, super)) { + return false; + } + } + } + + if (!is_instance_of(model, uris, statement, node, klass)) { + VERRORF(statement, + "Node %s is not an instance of %s\n", + serd_node_get_string(node), + serd_node_get_string(klass)); + return false; + } + + return true; // FIXME? +} + +static bool +check_type(SerdModel* model, + const URIs* uris, + const SerdStatement* statement, + const SerdNode* node, + const SerdNode* type) +{ + if (serd_node_equals(type, uris->rdfs_Resource) || + serd_node_equals(type, uris->owl_Thing)) { + return true; // Trivially true for everything (more or less) + } + + if (serd_node_get_type(node) == SERD_LITERAL) { + if (serd_node_equals(type, uris->rdfs_Literal)) { + return true; // Trivially true for a literal + } else if (serd_node_equals(type, uris->rdf_PlainLiteral)) { + if (serd_node_get_datatype(node)) { + VERRORF(statement, + "Literal \"%s\" should be plain, but has datatype " + "<%s>\n", + serd_node_get_string(node), + serd_node_get_string(serd_node_get_datatype(node))); + return false; + } + return true; + } else if (!serd_model_ask(model, + type, + uris->rdf_type, + uris->rdfs_Datatype, + NULL)) { + VERRORF(statement, + "Literal \"%s\" where instance of <%s> expected\n", + serd_node_get_string(node), + serd_node_get_string(type)); + return false; + } else { + return literal_is_valid(model, uris, statement, node, type); + } + } else if (serd_node_get_type(node) == SERD_URI) { + if (is_subdatatype(model, uris, type, uris->xsd_anyURI)) { + /* Type is any URI and this is a URI, so pass. Restrictions on + anyURI subtypes are not currently checked (very uncommon). */ + return true; + } else { + return check_instance_type(model, uris, statement, node, type); + } + } else { + return check_instance_type(model, uris, statement, node, type); + } + + VERRORF(statement, + "Unhandled case checking if %s is of type %s\n", + serd_node_get_string(node), + serd_node_get_string(type)); + + return false; +} + +static uint64_t +count_non_blanks(SerdRange* i, SerdField field) +{ + uint64_t n = 0; + SERD_FOREACH (s, i) { + const SerdNode* node = serd_statement_get_node(s, field); + if (serd_node_get_type(node) != SERD_BLANK) { + ++n; + } + } + return n; +} + +static int +check_statement(SerdModel* model, + const URIs* uris, + const SerdStatement* statement) +{ + int st = 0; + + const SerdNode* subj = serd_statement_get_subject(statement); + const SerdNode* pred = serd_statement_get_predicate(statement); + const SerdNode* obj = serd_statement_get_object(statement); + + // Check that predicate is defined as some kind of propety + bool is_property = false; + SerdRange* t = serd_model_range(model, pred, uris->rdf_type, NULL, NULL); + SERD_FOREACH (s, t) { + const SerdNode* type = serd_statement_get_object(s); + if (is_subclass(model, uris, type, uris->rdf_Property)) { + is_property = true; + break; + } + } + serd_range_free(t); + + if (!is_property) { + st = VERROR(statement, "Use of undefined property\n"); + } + + if (serd_node_equals(pred, uris->rdf_type)) { + // Type statement, check that object is a valid instance of type + check_type(model, uris, statement, subj, obj); + } + + const bool is_ObjectProperty = serd_model_ask( + model, pred, uris->rdf_type, uris->owl_ObjectProperty, 0); + const bool is_FunctionalProperty = serd_model_ask( + model, pred, uris->rdf_type, uris->owl_FunctionalProperty, 0); + const bool is_InverseFunctionalProperty = + serd_model_ask(model, + pred, + uris->rdf_type, + uris->owl_InverseFunctionalProperty, + 0); + const bool is_DatatypeProperty = serd_model_ask( + model, pred, uris->rdf_type, uris->owl_DatatypeProperty, 0); + + if (!serd_model_ask(model, pred, uris->rdfs_label, NULL, NULL)) { + st = VERRORF(statement, + "Property <%s> has no label\n", + serd_node_get_string(pred)); + } + + if (is_DatatypeProperty && serd_node_get_type(obj) != SERD_LITERAL) { + st = VERROR(statement, "Datatype property with non-literal value\n"); + } + + if (is_ObjectProperty && serd_node_get_type(obj) == SERD_LITERAL) { + st = VERROR(statement, "Object property with literal value\n"); + } + + if (is_FunctionalProperty) { + SerdRange* o = serd_model_range(model, subj, pred, NULL, NULL); + const uint64_t n = count_non_blanks(o, SERD_OBJECT); + if (n > 1) { + st = VERRORF(statement, "Functional property with %u objects\n", n); + } + serd_range_free(o); + } + + if (is_InverseFunctionalProperty) { + SerdRange* s = serd_model_range(model, NULL, pred, obj, NULL); + const unsigned n = count_non_blanks(s, SERD_SUBJECT); + if (n > 1) { + st = VERRORF(statement, + "Inverse functional property with %u subjects\n", + n); + } + serd_range_free(s); + } + + if (serd_node_get_type(obj) == SERD_LITERAL && + !literal_is_valid( + model, uris, statement, obj, serd_node_get_datatype(obj))) { + /* st = VERRORF(statement, "Literal does not match datatype\n"); */ + } + + // Check range + SerdRange* r = serd_model_range(model, pred, uris->rdfs_range, NULL, NULL); + SERD_FOREACH (s, r) { + const SerdNode* range = serd_statement_get_object(s); + if (has_explicit_type(model, uris, obj, range)) { + continue; // Explicit type will be checked, avoid duplicate reports + } + + if (!check_type(model, uris, statement, obj, range)) { + VNOTEF(serd_range_front(r), + "Range of <%s> defined here\n", + serd_node_get_string(pred)); + } + } + serd_range_free(r); + + // Check domain + SerdRange* d = serd_model_range(model, pred, uris->rdfs_domain, NULL, NULL); + SERD_FOREACH (s, d) { + const SerdNode* domain = serd_statement_get_object(s); + if (has_explicit_type(model, uris, subj, domain)) { + continue; // Explicit type will be checked, avoid duplicate reports + } + + if (!check_type(model, uris, statement, subj, domain)) { + VNOTEF(serd_range_front(d), + "Domain of <%s> defined here\n", + serd_node_get_string(pred)); + } + } + serd_range_free(d); + + return st; +} + +static int +check_properties(SerdModel* model, URIs* uris) +{ + int st = 0; + SerdRange* i = serd_model_range(model, NULL, NULL, NULL, NULL); + SERD_FOREACH (statement, i) { + st = check_statement(model, uris, statement) || st; + } + serd_range_free(i); + + return st; +} + +static int +cardinality_error(const SerdStatement* statement, + const SerdStatement* restriction_statement, + const SerdNode* property, + const SerdNode* instance, + const unsigned actual_values, + const char* comparison, + const unsigned expected_values) +{ + const int st = VERRORF(statement, + "Property <%s> has %u %s %u values\n", + serd_node_get_string(property), + actual_values, + comparison, + expected_values); + VNOTE(restriction_statement, "Restriction here\n"); + return st; +} + +static int +check_class_restriction(SerdModel* model, + const URIs* uris, + const SerdNode* restriction, + const SerdStatement* statement, + const SerdNode* instance) +{ + int st = 0; + const SerdNode* prop = serd_model_get( + model, restriction, uris->owl_onProperty, NULL, NULL); + if (!prop) { + return 0; + } + + const unsigned values = serd_model_count(model, instance, prop, NULL, NULL); + + // Check exact cardinality + const SerdStatement* c = serd_model_get_statement( + model, restriction, uris->owl_cardinality, NULL, NULL); + if (c) { + const SerdNode* card = serd_statement_get_object(c); + const unsigned count = atoi(serd_node_get_string(card)); + if (check(values != count)) { + st = cardinality_error( + statement, c, prop, instance, values, "!=", count); + } + } + + // Check minimum cardinality + const SerdStatement* l = serd_model_get_statement( + model, restriction, uris->owl_minCardinality, NULL, NULL); + if (l) { + const SerdNode* card = serd_statement_get_object(l); + const unsigned count = atoi(serd_node_get_string(card)); + if (check(values < count)) { + st = cardinality_error( + statement, l, prop, instance, values, "<", count); + } + } + + // Check maximum cardinality + const SerdStatement* u = serd_model_get_statement( + model, restriction, uris->owl_maxCardinality, NULL, NULL); + if (u) { + const SerdNode* card = serd_statement_get_object(u); + const unsigned count = atoi(serd_node_get_string(card)); + if (check(values > count)) { + st = cardinality_error( + statement, u, prop, instance, values, ">", count); + } + } + + // Check someValuesFrom + const SerdStatement* s = serd_model_get_statement( + model, restriction, uris->owl_someValuesFrom, 0, 0); + if (s) { + const SerdNode* some = serd_statement_get_object(s); + + SerdRange* v = serd_model_range(model, instance, prop, NULL, NULL); + bool found = false; + SERD_FOREACH (i, v) { + const SerdNode* value = serd_statement_get_object(i); + if (check_type(model, uris, statement, value, some)) { + found = true; + break; + } + } + if (check(!found)) { + st = VERRORF(statement, + "%s has no <%s> values of type <%s>\n", + serd_node_get_string(instance), + serd_node_get_string(prop), + serd_node_get_string(some)); + VNOTE(s, "Restriction here\n"); + } + serd_range_free(v); + } + + // Check allValuesFrom + const SerdStatement* a = serd_model_get_statement( + model, restriction, uris->owl_allValuesFrom, 0, 0); + if (a) { + ++n_restrictions; + const SerdNode* all = serd_statement_get_object(a); + + SerdRange* v = serd_model_range(model, instance, prop, NULL, NULL); + SERD_FOREACH (i, v) { + const SerdNode* value = serd_statement_get_object(i); + if (!check_type(model, uris, statement, value, all)) { + st = VERRORF(i, + "<%s> value not of type <%s>\n", + serd_node_get_string(prop), + serd_node_get_string(all)); + VNOTE(a, "Restriction here\n"); + break; + } + } + serd_range_free(v); + } + + return st; +} + +static int +missing_arg(const char* name, char opt) +{ + CERRORF("option requires an argument -- '%c'\n", opt); + return print_usage(name, true); +} + +static void +init_uris(URIs* uris) +{ +#define URI(prefix, suffix) \ + uris->prefix##_##suffix = serd_new_uri(NS_##prefix #suffix) + + URI(foaf, Document); + URI(owl, Class); + URI(owl, DatatypeProperty); + URI(owl, FunctionalProperty); + URI(owl, InverseFunctionalProperty); + URI(owl, ObjectProperty); + URI(owl, Restriction); + URI(owl, Thing); + URI(owl, allValuesFrom); + URI(owl, cardinality); + URI(owl, equivalentClass); + URI(owl, maxCardinality); + URI(owl, minCardinality); + URI(owl, onDatatype); + URI(owl, onProperty); + URI(owl, someValuesFrom); + URI(owl, withRestrictions); + URI(rdf, PlainLiteral); + URI(rdf, Property); + URI(rdf, first); + URI(rdf, rest); + URI(rdf, type); + URI(rdfs, Class); + URI(rdfs, Datatype); + URI(rdfs, Literal); + URI(rdfs, Resource); + URI(rdfs, domain); + URI(rdfs, label); + URI(rdfs, range); + URI(rdfs, subClassOf); + URI(xsd, anyURI); + URI(xsd, float); + URI(xsd, decimal); + URI(xsd, double); + URI(xsd, maxExclusive); + URI(xsd, maxInclusive); + URI(xsd, minExclusive); + URI(xsd, minInclusive); + URI(xsd, pattern); + URI(xsd, string); +} + +int +main(int argc, char** argv) +{ + if (argc < 2) { + return print_usage(argv[0], true); + } + + int a = 1; + long stack_size = 4194304; + for (; a < argc && argv[a][0] == '-'; ++a) { + if (argv[a][1] == 'h') { + return print_usage(argv[0], false); + } else if (argv[a][1] == 'k') { + if (++a == argc) { + return missing_arg(argv[0], 'k'); + } + stack_size = strtol(argv[a], NULL, 10); + if (stack_size <= 0 || stack_size == LONG_MAX) { + CERRORF("stack size `%ld' out of range\n", stack_size); + return 1; + } + } else if (argv[a][1] == 'v') { + return print_version(); + } else { + CERRORF("invalid option -- '%s'\n", argv[a] + 1); + return print_usage(argv[0], true); + } + } + + SerdWorld* world = serd_world_new(); + SerdModel* model = serd_model_new(world, SERD_SPO | SERD_OPS, false); + SerdEnv* env = serd_env_new(NULL); + SerdInserter* inserter = serd_inserter_new(model, env, NULL); + SerdReader* reader = serd_reader_new( + world, SERD_TURTLE, serd_inserter_get_sink(inserter), stack_size); + + for (; a < argc; ++a) { + const char* input = argv[a]; + char* in_path = absolute_path(input); + + if (!in_path) { + CERRORF("unable to open file %s\n", input); + continue; + } + + SerdNode* base_uri_node = serd_new_file_uri(in_path, NULL); + + serd_env_set_base_uri(env, base_uri_node); + SerdStatus st = serd_reader_start_file(reader, input, true); + st = serd_reader_read_document(reader); + st = serd_reader_finish(reader); + + if (st) { + CERRORF("error reading %s: %s\n", in_path, serd_strerror(st)); + return 1; + } + + serd_node_free(base_uri_node); + free(in_path); + } + serd_reader_free(reader); + serd_env_free(env); + + URIs uris; + init_uris(&uris); + +#ifndef HAVE_PCRE + fprintf(stderr, "warning: Built without PCRE, datatypes not checked.\n"); +#endif + + const int st = check_properties(model, &uris); + + printf("Found %d errors among %d files (checked %d restrictions)\n", + n_errors, + argc - 1, + n_restrictions); + + serd_model_free(model); + serd_world_free(world); + + return !st && n_errors == 0 ? 0 : 2; +} @@ -67,6 +67,19 @@ def configure(conf): defines = ['_POSIX_C_SOURCE=200809L'], mandatory = False) + autowaf.check_pkg(conf, 'libpcre', uselib_store='PCRE', mandatory=False) + if conf.env.HAVE_PCRE: + if conf.check(cflags=['-pthread'], mandatory=False): + conf.env.PTHREAD_CFLAGS = ['-pthread'] + if conf.env.CC_NAME != 'clang': + conf.env.PTHREAD_LINKFLAGS = ['-pthread'] + elif conf.check(linkflags=['-lpthread'], mandatory=False): + conf.env.PTHREAD_CFLAGS = [] + conf.env.PTHREAD_LINKFLAGS = ['-lpthread'] + else: + conf.env.PTHREAD_CFLAGS = [] + conf.env.PTHREAD_LINKFLAGS = [] + dump = Options.options.dump.split(',') if 'all' in dump or 'iter' in dump: conf.define('SERD_DEBUG_ITER', 1) @@ -180,18 +193,24 @@ def build(bld): # Utilities if bld.env.BUILD_UTILS: - obj = bld(features = 'c cprogram', - source = 'src/serdi.c', - target = 'serdi', - includes = ['.', './src'], - use = 'libserd', - lib = lib_args['lib'], - install_path = '${BINDIR}') - if not bld.env.BUILD_SHARED or bld.env.STATIC_PROGS: - obj.use = 'libserd_static' - if bld.env.STATIC_PROGS: - obj.env.SHLIB_MARKER = obj.env.STLIB_MARKER - obj.linkflags = ['-static'] + for i in ['serdi', 'serd_validate']: + obj = bld(features = 'c cprogram', + source = 'src/%s.c' % i, + target = i, + includes = ['.', './src'], + use = 'libserd', + lib = lib_args['lib'], + linkflags = [], + install_path = '${BINDIR}') + if not bld.env.BUILD_SHARED or bld.env.STATIC_PROGS: + obj.use = 'libserd_static' + if bld.env.STATIC_PROGS: + obj.env.SHLIB_MARKER = obj.env.STLIB_MARKER + obj.linkflags += ['-static'] + if i == 'serd_validate': + autowaf.use_lib(bld, obj, 'PCRE') + obj.cflags = bld.env.PTHREAD_CFLAGS + obj.linkflags += bld.env.PTHREAD_LINKFLAGS # Documentation autowaf.build_dox(bld, 'SERD', SERD_VERSION, top, out) |