How does RDFlib handle collections?

I wonder if anyone else has come across the following problem with RDFlib. I have imported the Person and Physical Address shapes from the shacl-schema website, which is a port of schema.org to shacl. Using the shacl-playground it copes fine with the sh:or construct (which is a collection), but RDFlib seems to have a problem. SHACL input (snippet in turtle format):

schema:PersonShape
  rdf:type rdfs:Class ;
  rdf:type sh:NodeShape ;
  rdfs:comment "A person (alive, dead, undead, or fictional)."^^rdf:HTML ;
  rdfs:label "Person" ;
  sh:targetClass schema:Person ;
  rdfs:subClassOf schema:Thing ;
  owl:equivalentClass <http://xmlns.com/foaf/0.1/Person> ;
  sh:property [
      sh:path schema:address ;
      sh:description "Physical address of the item."^^rdf:HTML ;
      sh:name "address" ;
      sh:or (
          [
            sh:class schema:PostalAddress;
          ]
          [
            sh:datatype xsd:string;
          ]
        ) ;
    ] ;

But solid-file-client (which uses RDFlib under the hood) seems to create a blank node that leads nowhere every time it comes across the sh:or construct.

<http://schema.org/PersonShape> <http://www.w3.org/2002/07/owl#equivalentClass> <http://xmlns.com/foaf/0.1/Person> .
_:_g_L18C687 <http://www.w3.org/ns/shacl#path> <http://schema.org/address> .
_:_g_L18C687 <http://www.w3.org/ns/shacl#description> "Physical address of the item."^^<http://www.w3.org/1999/02/22-rdf-syntax-ns#HTML> .
_:_g_L18C687 <http://www.w3.org/ns/shacl#name> "address" .
_:_g_L23C835 <http://www.w3.org/ns/shacl#class> <http://schema.org/PostalAddress> .
_:_g_L26C902 <http://www.w3.org/ns/shacl#datatype> <http://www.w3.org/2001/XMLSchema#string> .
_:_g_L18C687 <http://www.w3.org/ns/shacl#or> _:0 .
<http://schema.org/PersonShape> <http://www.w3.org/ns/shacl#property> _:_g_L18C687 .

I can’t work out how to go from _:0 to _:_g_L23C835 and _:_g_L26C902.

As an aside I performed the same query in the python RDFlib and it has implemented collections as an RDF list.

I have got to the bottom of the matter. When I use a direct call to RDFlib, instead of fetchAndParse, I noticed that the object of the sh:or predicate is a Collection. When I retrieve the information from the Collection, I have the pointers that I need.

fetch.load(resource).then(data => {
    let res = store.statementsMatching(undefined, undefined, undefined, undefined)
    res.forEach(row => {
        console.log(row)
        console.log("object isa", row.object.termType)
        if (row.object.termType === "Collection") {
            row.object.elements.forEach(part => {
                console.log(part)
            })
        }
        console.log("-----------")
    });

Which yields:

Statement {
  subject: BlankNode { termType: 'BlankNode', id: '_g_L15C608', value: '_g_L15C608' },
  predicate: 
   NamedNode {
     termType: 'NamedNode',
     value: 'http://www.w3.org/ns/shacl#or' },
  object: 
   Collection {
     termType: 'Collection',
     id: 4,
     elements: [ [Object], [Object] ],
     closed: false },
  why: 
   NamedNode {
     termType: 'NamedNode',
     value: 'https://localhost:8443/solid-components/someSHapes.ttl' } }
object isa Collection
BlankNode { termType: 'BlankNode', id: '_g_L20C756', value: '_g_L20C756' }
BlankNode { termType: 'BlankNode', id: '_g_L23C823', value: '_g_L23C823' }

(differences in the blankNode values is because of timing differences between the two runs)

The issue of using rdflib.js directly or using it indirectly through solid-file-client is a red herring. You can access the collection either way. For example, this code uses solid-file-client with your collection dereferencing code and it produces the same result as your code using rdflib.js directly.

const fc = require('solid-file-client')
var subj = "http://localhost/solid/t/shapes.ttl"

fc.fetchAndParse( subj ).then( kb => {
    let res = kb.statementsMatching(undefined, undefined, undefined, undefined)
    res.forEach(row => {
        console.log(row)
        console.log("object isa", row.object.termType)
        if (row.object.termType === "Collection") {
            row.object.elements.forEach(part => {
                console.log(part)
            })
        }
        console.log("-----------")
    });
}, err => console.log("could not fetch : "+err) ) ;
1 Like