Check a SKOS vocabulary against rules written in SHACL, the W3C language for saying what data must look like. Leave the rules empty to use the built-in SKOS quality rules, or paste your own, for example “every client is linked to at least one product”. The SKOS Explorer checks what SKOS itself requires; this page checks what you require.
Used when you send no shapes of your own. None of this is required by SKOS; these are the house rules most published vocabularies follow.
The shapes themselves, as a starting point for your own:
# SKOS quality rules — the built-in rule set of the SKOS SHACL worker.
# These go beyond the SKOS Reference: nothing here is required by SKOS itself,
# they are the house rules most published vocabularies follow.
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dct: <http://purl.org/dc/terms/> .
@prefix q: <https://devops.majbase.com/skos-explorer/shacl/rules#> .
q:ConceptShape a sh:NodeShape ;
rdfs:label "Concept" ;
sh:targetClass skos:Concept ;
sh:property [
sh:name "has-pref-label" ;
sh:path skos:prefLabel ;
sh:minCount 1 ;
sh:severity sh:Violation ;
sh:message "A concept needs at least one preferred label." ] ;
sh:property [
sh:name "one-pref-label-per-language" ;
sh:path skos:prefLabel ;
sh:uniqueLang true ;
sh:severity sh:Violation ;
sh:message "A concept may have only one preferred label per language." ] ;
sh:property [
sh:name "labels-have-a-language" ;
sh:path [ sh:alternativePath ( skos:prefLabel skos:altLabel skos:hiddenLabel ) ] ;
sh:datatype rdf:langString ;
sh:severity sh:Warning ;
sh:message "A label should say which language it is in." ] ;
sh:property [
sh:name "labels-are-not-empty" ;
sh:path [ sh:alternativePath ( skos:prefLabel skos:altLabel skos:hiddenLabel ) ] ;
sh:pattern "\\S" ;
sh:severity sh:Violation ;
sh:message "A label must not be empty." ] ;
sh:property [
sh:name "has-definition" ;
sh:path [ sh:alternativePath ( skos:definition skos:scopeNote ) ] ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "A concept should have a definition or a scope note." ] ;
sh:property [
sh:name "in-a-scheme" ;
sh:path [ sh:alternativePath ( skos:inScheme skos:topConceptOf [ sh:inversePath skos:hasTopConcept ] ) ] ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "A concept should belong to a concept scheme." ] ;
sh:property [
sh:name "has-a-place-in-the-hierarchy" ;
sh:path [ sh:alternativePath ( skos:broader [ sh:inversePath skos:narrower ]
skos:topConceptOf [ sh:inversePath skos:hasTopConcept ] ) ] ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "A concept should have a broader concept or be a top concept of its scheme." ] ;
sh:property [
sh:name "links-point-to-resources" ;
sh:path [ sh:alternativePath ( skos:broader skos:narrower skos:related skos:inScheme skos:topConceptOf
skos:exactMatch skos:closeMatch skos:broadMatch skos:narrowMatch skos:relatedMatch ) ] ;
sh:nodeKind sh:IRI ;
sh:severity sh:Violation ;
sh:message "A link between concepts must point to a resource with a URI, not to text or a blank node." ] ;
sh:property [
sh:name "one-notation" ;
sh:path skos:notation ;
sh:maxCount 1 ;
sh:severity sh:Info ;
sh:message "A concept usually has a single notation." ] .
q:ConceptSchemeShape a sh:NodeShape ;
rdfs:label "Concept scheme" ;
sh:targetClass skos:ConceptScheme ;
sh:property [
sh:name "scheme-has-a-name" ;
sh:path [ sh:alternativePath ( skos:prefLabel dct:title rdfs:label ) ] ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "A concept scheme should have a name (skos:prefLabel, dct:title or rdfs:label)." ] ;
sh:property [
sh:name "scheme-has-top-concepts" ;
sh:path [ sh:alternativePath ( skos:hasTopConcept [ sh:inversePath skos:topConceptOf ] ) ] ;
sh:minCount 1 ;
sh:severity sh:Info ;
sh:message "A concept scheme usually names its top concepts." ] .
q:CollectionShape a sh:NodeShape ;
rdfs:label "Collection" ;
sh:targetClass skos:Collection , skos:OrderedCollection ;
sh:property [
sh:name "collection-has-a-name" ;
sh:path [ sh:alternativePath ( skos:prefLabel rdfs:label ) ] ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "A collection should have a label." ] ;
sh:property [
sh:name "collection-has-members" ;
sh:path [ sh:alternativePath ( skos:member skos:memberList ) ] ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "A collection should have at least one member." ] .
sh:targetClass, sh:targetNode, sh:targetSubjectsOf, sh:targetObjectsOf), property paths, cardinality, value type, string, language, comparison and logical constraints, sh:node, qualified value shapes, sh:severity and sh:message.owl:imports. A shapes file that uses them is refused with the name of the term. A SPARQL query can be made arbitrarily slow and can ask the validator to fetch from the web, and nothing is ever fetched here.sh:targetClass skos:Concept reaches only resources typed skos:Concept, and skos:narrower is not read as the reverse of skos:broader: write the path both ways, as the built-in rules do, with sh:alternativePath and sh:inversePath.No key is needed. The full description is in the OpenAPI document.
curl -s -X POST https://devops.majbase.com/skos-explorer/shacl/api -F "file=@vocabulary.ttl"
verdict=$(curl -s -X POST https://devops.majbase.com/skos-explorer/shacl/api \
-F "file=@vocabulary.ttl" -F "shapes_file=@rules.ttl" | jq -r .verdict)
echo "SHACL: $verdict"
[ "$verdict" = "conforms" ] || [ "$verdict" = "warnings" ]
verdict: violations, warnings or conforms; totals counts results by severity; ruleset is skos-quality or custom.rules: one entry per kind of result with severity, rule (the shape's sh:name), message, path, constraint, count and up to 10 examples (focus and value, each with value, label, kind).summary: statements, shapes, results, resources_with_results; processing_time_ms, validator_version.{"error": "…"}: 400 unreadable input or shapes beyond SHACL Core, 422 the validation hit its time or memory limit, 429 rate limit, 503 the validation service cannot be reached.