How to write a sparql query with variable predicate? - sparql

Please have a look at the following example:
#prefix : <#> .
#prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
:Bob :score 78 .
:John :score 50 .
:rule1 :condition [
rdf:subject :score ;
rdf:predicate :notLessThan ;
rdf:object 60
];
:condition [
rdf:subject :score ;
rdf:predicate :notGreaterThan ;
rdf:object 80
];
:rating :passed .
:rule2 :condition [
rdf:subject :score ;
rdf:predicate :lessThan ;
rdf:object 60
];
:rating :failed .
I want to get the following output by spqrql query:
:Bob :rating :passed.
:John :rating :failed.
here rdf:predicate is changeable by the user, and its value may be:
lessThan, notLessThan, greaterThan, notGreaterThan
So how do I write this SPARQL statement based on dynamic predicate?
I don't have any ideas. Thank you for helping me.

This does work perfectly :
PREFIX : <#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
CONSTRUCT {
?student :rating ?rating .
} WHERE {
?student :score ?score .
?rule :rating ?rating .
OPTIONAL {
?rule :condition ?condition .
?condition rdf:subject :score .
?condition rdf:predicate ?predicate .
?condition rdf:object ?object .
# fails condition
FILTER (
! ( (?predicate = :lessThan && ?score < ?object ) ||
(?predicate = :notLessThan && ?score >= ?object ) ||
(?predicate = :greaterThan && ?score > ?object ) ||
(?predicate = :notGreaterThan && ?score <= ?object )
)
)
}
FILTER(!bound(?condition))
}
and we get the results:
:Bob :rating :passed .
:John :rating :failed .

Related

Make a UNION return a single result in SPARQL

I am trying to query for the sh:description of a property shape, and if there is none, I want to follow the path to a property and get the rdfs:comment (Also for sh:name and rdfs:label). My data looks like the following:
ex:File
a owl:Class ;
a sh:NodeShape ;
sh:property ex:File-name ;
ex:File-name
a sh:PropertyShape ;
sh:path ex:filename ;
sh:datatype xsd:string ;
sh:description "The name of the file" ;
sh:maxCount 1 ;
ex:filename
a rdf:Property ;
rdfs:label "Filename" ;
rdfs:comment "The file name" ;
.
I have the query below, but it returns two results. How can I modify it to only return a single result (preferring sh:description over rdfs:comment)?
PREFIX : <http://www.example.org/#>
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
select ?propertyShape ?property ?name ?description where {
:File sh:property ?propertyShape .
?propertyShape sh:path ?property .
{ ?propertyShape sh:name ?name } UNION { ?property rdfs:label ?name } .
{ ?propertyShape sh:description ?description } UNION { ?property rdfs:comment ?description } .
The above returns something like this:
propertyShape
property
name
description
:File-name
ex:filename
"Filename"
"The name of the file"
:File-name
ex:filename
"Filename"
"The file name"
I would like for it to return something like:
propertyShape
property
name
description
:File-name
ex:filename
"Filename"
"The name of the file"
You should be able to use a sequence of OPTIONAL blocks to do this:
SELECT ?propertyShape ?property ?name ?description WHERE {
:File sh:property ?propertyShape .
?propertyShape sh:path ?property .
OPTIONAL { ?propertyShape sh:name ?name }
OPTIONAL { ?property rdfs:label ?name }
OPTIONAL { ?propertyShape sh:description ?description }
OPTIONAL { ?property rdfs:comment ?description }
}
You'll still have to think about cases where there are multiple names/descriptions per property (this isn't guaranteed to return just a single result), but it should give you the "preferring sh:description over rdfs:comment" behavior.
Can't you just use path expressions such as
...
?propertyShape sh:name|(sh:path/rdfs:label) ?name .
?propertyShape sh:description|(sh:path/rdfs:comment) ?description .

SPARQL OPTIONAL rdf:rest*/rdf:first does not return the first list

I am trying to construct a query that will return everything in an rdf list, given a specific subject.
I have a query like this:
PREFIX : <https://example.com/#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX sh: <http://www.w3.org/ns/shacl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
select ?class ?propertyShape ?type where {
:ClassA rdfs:subClassOf ?class .
?class sh:property ?propertyShape .
OPTIONAL { ?propertyShape sh:xone/rdf:rest*/rdf:first/sh:class ?type . }
}
My data looks like this:
#prefix : <https://example.com/#> .
#prefix owl: <http://www.w3.org/2002/07/owl#> .
#prefix sh: <http://www.w3.org/ns/shacl#> .
:ClassA-shapeB
a sh:PropertyShape ;
sh:path :shapeB ;
sh:nodeKind sh:IRI ;
sh:xone (
[
sh:class :ClassB ;
]
[
sh:class :ClassC ;
]
) ;
.
:ClassA-shapeA
a sh:PropertyShape ;
sh:path :shapeA ;
sh:nodeKind sh:IRI ;
sh:xone (
[
sh:class :ClassB ;
]
[
sh:class :ClassC ;
]
) ;
.
:ClassA-shapeC
a sh:PropertyShape ;
sh:path :shapeA ;
sh:nodeKind sh:IRI ;
sh:xone (
[
sh:class :ClassB ;
]
[
sh:class :ClassC ;
]
) ;
.
:shapeA
a owl:ObjectProperty ;
.
:shapeB
a owl:ObjectProperty ;
.
:shapeC
a owl:ObjectProperty ;
.
:ClassA
a owl:Class ;
a sh:NodeShape ;
sh:property :ClassA-shapeA ;
sh:property :ClassA-shapeB ;
sh:property :ClassA-shapeC ;
rdfs:subClassOf :ClassA ;
.
:ClassC
a owl:Class ;
a sh:NodeShape ;
.
The query result looks like this:
class
propertyShape
type
:ClassA
:ClassA-shapeB
:ClassB
:ClassA
:ClassA-shapeB
:ClassC
:ClassA
:ClassA-shapeA
:ClassA
:ClassA-shapeC
:ClassB
:ClassA
:ClassA-shapeC
:ClassC
I would expect for there to also be two :ClassA-shapeA, having :ClassB and :ClassC like the other two, but it does not. The first propertyShape (alphabetically) always has it's types left out.
This query works as expected if I remove OPTIONAL, but that is needed to get the rest of the property's, (not included in this example).

How to determine if one range contains another?

Consider the following example:
:rangeA :lowerLimit :LeftMarginA ;
:upperLimit :RightMarginA .
:rangeB :lowerLimit :LeftMarginB ;
:upperLimit :RightMarginB .
:LeftMarginA :hasValue 20 ;
:RightMarginA :hasValue 80 .
:LeftMarginB :hasValue 30 ;
:RightMarginB :hasValue 60 .
How to get inference results using SPARQL or SWRL
:rangeA :Contains :rangeB
My idea is:
if (:LeftMarginB >= :LeftMarginA)
&& (:RightMarginB <= :RightMarginA)
then :rangeA :contains :rangeB
But how do I write this SPARQL or SWRL statement?
Thanks for help.
If you can write a SELECT query that selects one range that contains another, it's just one more step to convert it into a CONSTRUCT WHERE query that generates the triple that captures your inference. For instance:
Some Data
#prefix : <urn:ex:>
:x :lower 30 ; :upper 60 .
:y :lower 20 ; :upper 80 .
A query
prefix : <urn:ex:>
construct {
?a :contains ?b
} where {
?a :lower ?la ; :upper ?ua .
?b :lower ?lb ; :upper ?ub .
filter ( ?lb < ?la && ?ua < ?ub )
}
The result
#prefix : <urn:ex:> .
:x :contains :y .
I've tested and this does work:
prefix : <#>
CONSTRUCT { ?rangeA :contains ?rangeB . }
WHERE {
{
SELECT ?rangeA ?rangeB
WHERE {
?rangeA :lowerLimit ?LeftMarginA ;
:upperLimit ?RightMarginA .
?rangeB :lowerLimit ?LeftMarginB ;
:upperLimit ?RightMarginB .
?LeftMarginA :hasValue ?valueLA.
?RightMarginA :hasValue ?valueRA.
?LeftMarginB :hasValue ?valueLB.
?RightMarginB :hasValue ?valueRB.
Filter ((?valueLB >= ?valueLA) && (?valueRB <= ?valueRA)
&& (?rangeA !=?rangeB)) ​
}
}
}
But that doesn't take into account the inclusion of the boundary.
If consider boundary inclusion like :
:LeftMarginA :hasValue 20 ;
:inclusive True.
:RightMarginA :hasValue 80 ;
:inclusive False.
How can SPARQL statements be modified to work properly?
Thank you all, the final solution is as follows:
#-----rdf data
#prefix : <#> .
:rangeA :lowerLimit 20 ;
:lowerLimitInclusiveA false ;
:upperLimit 80 ;
:upperLimitInclusiveA false .
:rangeB :lowerLimit 20.5 ;
:lowerLimitInclusiveB false ;
:upperLimit 60 ;
:upperLimitInclusiveB false .
#---sparql query ========================
prefix : <#>
CONSTRUCT { ?rangeA :contains ?rangeB . }
WHERE {
{
SELECT ?rangeA ?rangeB
WHERE {
?rangeA :lowerLimit ?LeftMarginA ;
:lowerLimitInclusiveA ?lowerA ;
:upperLimit ?RightMarginA ;
:upperLimitInclusiveA ?upperA .
?rangeB :lowerLimit ?LeftMarginB ;
:lowerLimitInclusiveB ?lowerB ;
:upperLimit ?RightMarginB ;
:upperLimitInclusiveB ?upperB .
Filter (if(?lowerA,?LeftMarginB>=?LeftMarginA, ?LeftMarginB>?LeftMarginA)
&& if(?upperA, ?RightMarginB<=?RightMarginA, ?RightMarginB<?RightMarginA)
&& (?rangeA!= ?rangeB))
}
}
}

SPARQL query for individuals with same properties

I'd like to identify all individuals, that have the same properties as some other individuals. Imagine having different shopping lists with different items on them one can either buy or rent (object properties). To make things more complex, I also want to pay an exact amount for the parking and travel a certain distance (data properties). At the same time, there exist different stores offering different items.
Being a lazy person, I would like to identify the stores for each list, that offer all the items on the list.
I believe this is a generalization of this question but I somehow cannot wrap my head around how to do this.
I created some sample individuals:
#prefix : <http://www.shopping.org/model#> .
#prefix owl: <http://www.w3.org/2002/07/owl#> .
#prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
#prefix xml: <http://www.w3.org/XML/1998/namespace> .
#prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
#prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
#base <http://www.shopping.org/model> .
<http://www.shopping.org/model> rdf:type owl:Ontology .
# Object Properties
:buy rdf:type owl:ObjectProperty .
:rent rdf:type owl:ObjectProperty .
# Data properties
:distance rdf:type owl:DatatypeProperty .
:parking rdf:type owl:DatatypeProperty .
# Classes
:Product rdf:type owl:Class .
:ShoppingList rdf:type owl:Class .
:Store rdf:type owl:Class .
# Individuals
:Apples rdf:type owl:NamedIndividual ,
:Product .
:Cereal rdf:type owl:NamedIndividual ,
:Product .
:List1 rdf:type owl:NamedIndividual ,
:ShoppingList ;
:buy :Apples ,
:Milk ;
:distance "9.0"^^xsd:float ;
:parking "10.0"^^xsd:float .
:List2 rdf:type owl:NamedIndividual ,
:ShoppingList ;
:buy :Cereal ,
:Milk ;
:rent :TV ;
:distance "5.0"^^xsd:float ;
:parking "10.0"^^xsd:float .
:Milk rdf:type owl:NamedIndividual ,
:Product .
:Store1 rdf:type owl:NamedIndividual ,
:Store ;
:buy :Apples ,
:Cereal ,
:Milk ,
:TV ;
:distance "9.0"^^xsd:float ;
:parking "10.0"^^xsd:float .
:Store2 rdf:type owl:NamedIndividual ,
:Store ;
:buy :Cereal ,
:Milk ;
:rent :TV ;
:distance "5.0"^^xsd:float ;
:parking "10.0"^^xsd:float .
:TV rdf:type owl:NamedIndividual ,
:Product .
# General axioms
[ rdf:type owl:AllDisjointClasses ;
owl:members ( :Product
:ShoppingList
:Store
)
] .
and also tried some first queries. This is the best I have:
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX : <http://www.shopping.org/model#>
SELECT DISTINCT ?list ?store ?prop
WHERE {
?list a :ShoppingList .
?store a :Store .
?list ?prop [] .
FILTER NOT EXISTS {
?list ?prop ?value .
FILTER NOT EXISTS {
?store ?prop ?value .
}
}
}
ORDER BY ?list ?store
However, this query returns all combinations of stores and lists, since every store charges 10.0f for parking.
How can I filter only those stores, which meet all the requirements of a list?
An additional assumption is, that there may exist other business models than buy and rent, as well as other criteria, i.e. data properties, which are of interest, too. This is why I do not want to specify these properties, but want to use variables instead.
This query did what I intended to do:
PREFIXES [as above]
PREFIX : <http://www.shopping.org/model#>
SELECT DISTINCT ?list ?store
WHERE {
?list a :ShoppingList .
?store a :Store .
FILTER NOT EXISTS {
?compat a owl:DatatypeProperty
FILTER NOT EXISTS {
?list ?compat ?value .
?store ?compat ?value .
}
}
FILTER NOT EXISTS {
?subset a owl:ObjectProperty .
?list ?subset ?value .
FILTER NOT EXISTS {
?store ?subset ?value .
}
}
}
ORDER BY ?list ?store
My mistake was actually just that I defined the ?prop outside of the filters. This resulted in them being part of the solution, i.e. I was was unable to remove whole stores through the filter(s).
If you're willing to specific about the particular property that the things need to have in common, you can do something like this. I've cleaned up your data, because it didn't actually have the necessary prefix declarations. Please include complete data if you expect people to be able to work with what you're providing. I didn't add correct prefixes, but just enough to get things working.
Sample Data
#prefix : <urn:ex:> .
#prefix owl: <file:///home/taylorj/tmp/data.ttl> .
#prefix xsd: <file:///home/taylorj/tmp/data.ttl> .
:Store1 a owl:NamedIndividual , :Store ;
:buy :Apples , :Cereal , :Milk , :TV ;
:distance "9.0"^^owl:float ;
:parking "10.0"^^owl:float .
:List2 a owl:NamedIndividual , :ShoppingList ;
:buy :Cereal , :Milk ;
:distance "5.0"^^owl:float ;
:parking "10.0"^^owl:float ;
:rent :TV .
:List1 a owl:NamedIndividual , :ShoppingList ;
:buy :Apples , :Milk ;
:distance "9.0"^^owl:float ;
:parking "10.0"^^owl:float .
:Store2 a owl:NamedIndividual , :Store ;
:buy :Cereal , :Milk ;
:distance "5.0"^^owl:float ;
:parking "10.0"^^owl:float ;
:rent :TV .
Specific Solution
First, we can address the specific case where we need one individual to have a subset of the property values of another for one property (buy), and to have intersecting values for other properties (distance, parking).
Query
prefix : <urn:ex:>
select ?list ?store {
#-- For each list and store
?list a :ShoppingList .
?store a :Store .
#-- Check that they have the same distance and parking.
?distance ^:distance ?list, ?store .
?parking ^:parking ?list, ?store .
#-- Check that every item to buy (or rent) on the list is also
#-- in the store to buy (or rent).
filter not exists {
values ?get { :buy :rent }
?list ?get ?item .
filter not exists {
?store ?get ?item
}
}
}
Results
--------------------
| list | store |
====================
| :List1 | :Store1 |
| :List2 | :Store2 |
--------------------
General Approach
Often times it's helpful to think in terms of eliminating the results that you don't want. In this case, you have two kinds of properties: (i) properties where the individuals must have a compatible value, and (ii) properties where one individual must have a superset of the values of the other individual. Checking if either of those conditions doesn't hold is not too hard:
prefix : <urn:ex:>
select ?list ?store {
#-- For each list and store
?list a :ShoppingList .
?store a :Store .
#-- Check that for each "compatible property" ?compat,
#-- there must be some value that the ?list and the
#-- ?store have in common.
filter not exists {
values ?compat { :distance :parking }
filter not exists {
?list ?compat ?value .
?store ?compat ?value .
}
}
#-- Check that for each "subset property" ?subset,
#-- there is no value that the ?list contains that
#-- the store does not.
filter not exists {
values ?subset { :buy :rent }
?list ?subset ?value
filter not exists {
?store ?subset ?value
}
}
}
Results (the same)
--------------------
| list | store |
====================
| :List1 | :Store1 |
| :List2 | :Store2 |
--------------------

sparql query: get several values from a List

My query returns result dependent on which one of two sparql OPTIONS clauses come first for a List of Images. Although using Jena ARQ is not an option at this point, and I'd like to solve this with a pure SPARQL query, still I'd like to know how it could be solved with Jena as well.
My data presentation is attached below, the data may contain a list of images. My image representation is also below. I'm attaching my query as well.
The sprql query has two variables urlX, and urlY declared in the 2 OPTIONS blocks, if a List of images exists. Depending on which of the OPTIONS comes first, I get the value for that one variable, while the other one doesn't get reached. It seems the issue has to do with using OPTIONS clause. I'm not sure what else I can try instead, I'm far from being an expert on sparql queries. I want the query to do the following: if a collection of images is present, I want to see if both image sizes (dc:conformsTo) are present and get both urlX and urlY values, or get the ones that exist. Much appreciate your time.
My data representation:
#prefix dc: <http://purl.org/dc/terms/> .
#prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
#prefix lews: <http://lews.com/content/> .
#prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
lews:26331340 lews:name "the Human Good Luck Charm is Back"^^xsd:token ;
dc:created "2014-10-20T17:14:55.357-07:00"^^xsd:dateTime ;
dc:identifier "26331340"^^xsd:int ;
dc:modified "2016-08-04T13:43:00.897-07:00"^^xsd:dateTime ;
dc:title "the Human Good Luck Charm is Back" ;
dc:hasPart <http://lews.com/content/26331340#Images> ;
dc:abstract "As the World Series gets underway..." ;
dc:description "The super fan who rooted for the Royals is back to boost morale." ;
dc:subject "hoping for a World Series victory".
<http://lews.com/content/26331340#Images> dc:identifier "Images"^^xsd:token ;
rdf:first lews:26331375 ;
rdf:rest _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331376 ;
rdf:li <http://lews.com/content/26331340#Images> , _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331376 , _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331377 , _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331378 , _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331379 , _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331380 ;
a rdf:List .
_:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331376 rdf:first lews:26331376 ;
rdf:rest _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331377 .
_:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331377 rdf:first lews:26331377 ;
rdf:rest _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331378 .
_:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331378 rdf:first lews:26331378 ;
rdf:rest _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331379 .
_:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331379 rdf:first lews:26331379 ;
rdf:rest _:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331380 .
_:genid-15e530b0195547d9ac3f8e5e6785a747-x26331340Images26331380 rdf:first lews:26331380 ;
rdf:rest rdf:nil .
My image representation:
#prefix dc: <http://purl.org/dc/terms/> .
#prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
#prefix lews: <http://abcnews.com/content/> .
#prefix mrss: <http://search.yahoo.com/mrss/> .
#prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
lews:26331376 lews:name "141020_wn_donvan0_704x396.jpg"^^xsd:token ;
lews:section "wnt"^^xsd:token ;
lews:type "Image"^^xsd:token ;
dc:conformsTo "704x396"^^xsd:token ;
dc:created "2014-10-20T17:15:09.637-07:00"^^xsd:dateTime ;
dc:hasFormat <http://lews.go.com/images/WNT/141020_wn_donvan0_704x396.jpg> ;
dc:identifier "26331376"^^xsd:int ;
dc:isPartOf <http://lews.go.com/WNT> ;
dc:modified "2014-10-20T17:15:09.947-07:00"^^xsd:dateTime ;
dc:type "StillImage"^^xsd:token ;
mrss:height "396"^^xsd:int ;
mrss:width "704"^^xsd:int ;
xsd:date "2014-10-20"^^xsd:date ;
xsd:gMonthDay "--10-20"^^xsd:gMonthDay ;
xsd:gYear "2014"^^xsd:gYear ;
xsd:gYearMonth "2014-10"^^xsd:gYearMonth .
My query:
PREFIX dc: <http://purl.org/dc/terms/>
PREFIX mrss: <http://search.yahoo.com/mrss/>
PREFIX search: <http://www.openrdf.org/contrib/lucenesail#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX lews: <http://abcnews.com/content/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?id ?title ?description ?urlX ?urlY ?section ?imgName
?subject dc:identifier ?id.
OPTIONAL {?subject dc:title ?title.}
OPTIONAL {?subject dc:description ?description.}
OPTIONAL {?subject dc:isPartOf ?section.}
OPTIONAL {
?subject dc:hasPart ?imageCol.
?imageCol dc:identifier "Images"^^xsd:token.
OPTIONAL{
?imageCol rdf:li ?bnode.
?bnode rdf:first ?image.
?image lews:name ?imgName;
dc:conformsTo "4x3";
dc:hasFormat ?urlX.
}
OPTIONAL{
?imageCol rdf:li ?bnode.
?bnode rdf:first ?image.
?image lews:name ?imgName;
dc:conformsTo "16x9";
dc:hasFormat ?urlY.
}
}
}
LIMIT ${limit}
If I understood correctly what it is you want, you just need to group the optionals differently:
PREFIX dc: <http://purl.org/dc/terms/>
PREFIX mrss: <http://search.yahoo.com/mrss/>
PREFIX search: <http://www.openrdf.org/contrib/lucenesail#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX lews: <http://abcnews.com/content/>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
SELECT ?id ?title ?description ?urlX ?urlY ?section ?imgName {
?subject dc:identifier ?id.
OPTIONAL { ?subject dc:title ?title. }
OPTIONAL { ?subject dc:description ?description. }
OPTIONAL { ?subject dc:isPartOf ?section. }
OPTIONAL {
?subject dc:hasPart ?imageCol.
?imageCol dc:identifier "Images"^^xsd:token.
OPTIONAL {
?imageCol rdf:li ?bnode.
?bnode rdf:first ?image.
?image lews:name ?imgName;
# Here we optionally bind ?urlX and/or ?urlY
OPTIONAL {
?image dc:conformsTo "4x3";
dc:hasFormat ?urlX.
}
OPTIONAL {
?image dc:conformsTo "16x9";
dc:hasFormat ?urlY.
}
}
}
}