SKOS SHACL Validator

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.

The example is the company vocabulary with a new client and a new team that are not finished. SKOS has nothing against either; the company's own rules do.

📜 The built-in SKOS quality rules

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.

  • Violations: a concept without a preferred label; two preferred labels in one language; an empty label; a link between concepts that points to text or a blank node.
  • Warnings: a label without a language tag; a concept with no definition or scope note, outside any concept scheme, or with no place in the hierarchy; a concept scheme or collection without a name; a collection without members.
  • Notes: a concept with several notations; a concept scheme that names no top concepts.

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." ] .
🔍 What is run, and what is not
  • SHACL Core. Targets (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.
  • Not accepted: SPARQL-based constraints and targets, SHACL rules, functions and JavaScript, and 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.
  • No inference. Shapes see the statements as written. 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.
  • SKOS only knows “related”. A rule such as “every team has exactly one lead” needs the vocabulary to say who the lead is, and SKOS has no property for that. The company example works around it with position in the tree; for typed relations the vocabulary itself has to use more than SKOS.
  • Limits. 1024 KB and 60,000 statements for the vocabulary, 256 KB for the shapes, 45 seconds per validation, 60 requests per hour per address, shared with the other SKOS tools. Large vocabularies take a while: about 17 seconds for 50,000 statements with the built-in rules.
  • Privacy. The vocabulary and the shapes are sent to a validation service on our own second server, processed in memory for the one request and not stored or logged.
💡 Show API usage examples

No key is needed. The full description is in the OpenAPI document.

Built-in rules

curl -s -X POST https://devops.majbase.com/skos-explorer/shacl/api -F "file=@vocabulary.ttl"

Your own rules, failing a build on violations

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" ]

Response fields

  • 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.
  • Errors come back as {"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.