Hydra and SHACL - Part 2 - IRI Templates

In the previous post I presented the simplest functionality of loading remote form contents by having SHACL property shape reference a Hydra Core collection.

In the second part I will extend that example to create a form with multiple connected dropdowns, where each one is only populated when other(s) have been selected, which is a common scenario seen in (web) applications.

TL;DR; can I see it working?

The screenshot below links to Shaperone Playground which implements the ideas described in the subsequent paragraphs.

shaperone playground

Filtering collections with Hydra

In addition to hydra:collection, the Hydra Core vocabulary comes with another general-purpose property hydra:search. Unlike most predicates which would link to another resource, identified by a concrete URI, its objects are instances of URI Templates, defined by RFC6570.

For example, let’s have a “State collection” resource which returns country’s first-level administrative division. It would come with a search template so that clients can construct filtered URIs:

The client must provide template values to a Hydra library which will return a URI fit for dereferencing. This is called expansion by the RFC6570. A Hydra client will take a graph node with values being attached to that node using the hydra:property as defined by the template and match those property/object pairs to the template variables.

Here’s an example of such a template variable model, where JSON-LD @context has been constructed from the hydra:mapping, although the JSON keys may be irrelevant for the expansion if the implementation only relies on the actual graph data.

Combine this with the template above to get

/states?country=http%3A%2F%2Fwww.wikidata.org%2Fentity%2FQ27

Read more about Hydra’s template here

Connecting form fields

The idea is simple:

  1. A SHACL Shape describes a graph structure
  2. A form can be generated for agents (usu. humans) to create an instance of such a graph
  3. Use the created graph to expand a template

Now, a form in such a scenario could simply be used to filter a collection for display, but I propose to short-circuit it back into the form itself so that the filtered collection, when dereferenced, provides values for other fields.

The Person shape above has two properties. The first will generate a dropdown with a selection of countries as described in the first Hydra+SHACL post. The second, while it’s also going to render a dropdown, will not be populated until a country is selected (hydra:required true).

The glue here is matching property shared between sh:path of the upstream field and hydra:property of the downstream’s search template. In other words, when the form’s graph node receives the value for the schema:addressCountry predicate, the “states” will be loaded.

Less APIs, more Web Standards!

Again this time, the playground example does not “talk” to an actual API but instead runs SPARQL queries encoded into query string parameters of Wikidata’s query endpoint. The trick is to replace a URI of the variable with a URI Template placeholder. Just gotta make sure that the braces are not percent-encoded.

The query to load states is simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
prefix hydra: <http://www.w3.org/ns/hydra/core#>

CONSTRUCT {
  ?col a hydra:Collection .
  ?col hydra:member ?division .
  ?division rdfs:label ?label .
} WHERE {
  BIND ( <urn:contry:collection> as ?col )

  <{COUNTRY}> wdt:P150 ?division .
  ?division rdfs:label ?label .
  filter ( lang(?label) IN ( 'en', 'de', 'fr', 'pl', 'es' ) )
}

Loading cities is slightly more complicated, accounting for deeper graphs where a state is the root and also various types of cities recognised by Wikidata.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
prefix hydra: <http://www.w3.org/ns/hydra/core#>

CONSTRUCT {
  ?col a hydra:Collection .
  ?col hydra:member ?city .
  ?city rdfs:label ?label .
} WHERE {
  BIND ( <urn:contry:collection> as ?col )

  <STATE> wdt:P150* ?city .
  ?city rdfs:label ?label .
  ?city wdt:P31 ?cityType .
  ?cityType wdt:P279 wd:Q515 .

  filter ( lang(?label) IN ( 'en', 'de', 'fr', 'pl', 'es' ) )
}

Tried as I might, the cities query does not work for every country. United States, Germany and Poland are fine. On the other hand, for Colombia and Australia it finds no cities at all. Queries for Australian cities are also surprisingly slow…

It is not important for the example, but I would be curious to learn from a Wikidata expert how it can be improved.

Comments