Search code examples
sparqlrdfjson-ld

FILTER duplicate head bindings in RDF Collection CONSTRUCT with SPARQL 1.1


I am trying to CONSTRUCT complete RDF Collections with a SPARQL 1.1 property path. The property path examples that I have seen are able to get the rdf:first nodes, but I have yet to see one that is able to get the whole chain, including the bnodes. The key parts of the query look like this:

PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX sc: <http://iiif.io/api/presentation/2#>

CONSTRUCT {?range sc:hasCanvases ?listid .
?listid rdf:first ?canvas .
?listid rdf:rest ?mid .
?mid rdf:rest ?node .
?node rdf:first ?canvas .
?node rdf:rest ?last .
[...]}

WHERE {values ?range {<http://some.uri>}
?range sc:hasCanvases ?listid .
values ?e { rdf:first rdf:rest } 
?listid rdf:rest* ?mid . 
?mid ?e ?node FILTER (?mid != ?node). 
?listid rdf:first ?first .
?node rdf:first ?canvas .
?node rdf:rest ?last .
[...]}

It mostly works, except that the ?listid node binds to every rdf:rest ?mid object in the property path.

There is a referencedOnce constraint in the jsonld.fromRDF method that makes these extra head triples problematic for list reconstruction with that library. I have tried a variety of subqueries, property paths and FILTERS, but am quite stuck on this. Is it possible?


Solution

  • It's not exactly clear what you're asking, but it sounds like you want to write a construct query that will contain a list that's present in the data. It's much easier if you provide sample data, and if you show the complete query that you're trying to use, because then we have much less to guess about. Under this assumption, let's start with some data. Here are two things with a property relating each one to a list of values:

    @prefix : <urn:ex:>
    
    :a :hasList (1 2 3 4 5) .
    :b :hasList (6 7 8 9 0) .
    

    Here's a query that retrieves the list associated with :a and constructs it:

    prefix : <urn:ex:>
    prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
    
    construct {
      ?mid rdf:first ?value ;
           rdf:rest ?tail .
    }
    where {
      :a :hasList ?list .
      ?list rdf:rest* ?mid .
      ?mid rdf:first ?value .
      ?mid rdf:rest ?tail .
    }
    

    And the results:

    <rdf:RDF
        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns="urn:ex:">
      <rdf:Description>
        <rdf:rest rdf:parseType="Resource">
          <rdf:rest rdf:parseType="Resource">
            <rdf:rest rdf:parseType="Resource">
              <rdf:rest rdf:parseType="Resource">
                <rdf:rest rdf:resource="http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"/>
                <rdf:first rdf:datatype="http://www.w3.org/2001/XMLSchema#integer"
                >5</rdf:first>
              </rdf:rest>
              <rdf:first rdf:datatype="http://www.w3.org/2001/XMLSchema#integer"
              >4</rdf:first>
            </rdf:rest>
            <rdf:first rdf:datatype="http://www.w3.org/2001/XMLSchema#integer"
            >3</rdf:first>
          </rdf:rest>
          <rdf:first rdf:datatype="http://www.w3.org/2001/XMLSchema#integer"
          >2</rdf:first>
        </rdf:rest>
        <rdf:first rdf:datatype="http://www.w3.org/2001/XMLSchema#integer"
        >1</rdf:first>
      </rdf:Description>
    </rdf:RDF>
    

    There where part can even be made a bit more concise, since you don't use the value of ?list:

      :a :hasList/rdf:rest* ?mid .
      ?mid rdf:first ?value .
      ?mid rdf:rest ?tail .