Generating websites with SPARQL and Snowman, part 2
With Rhizome's excellent ArtBase SPARQL endpoint.
In part one of this two-part series, we saw how the open source Snowman static web site generator can generate websites with data from a SPARQL endpoint. I showed how I created a sample website project with its snowman new
command and then reconfigured the project to retrieve a list of artists from the Rhizome ArtBase endpoint, a repository of data about digital artworks since 1999. Here in part two I will build on that to add lists of artists’ works with links to Rhizome pages about them.
Add lists of artists works with links to more information
To add these lists of the artists’ works under their names, I started by removing the last two lines of the project’s views.yaml
file (which was generated by the original snowman new
command) and the templates/static.html
file that the last line pointed to because I didn’t need that additional view:
views:
- output: "index.html"
query: "index.rq"
template: "index.html"
- output: "static/index.html"
template: "static.html"
The Snowman github readme file tells you more about views.
A lot of incremental development in Snowman consists of adding to a query such as the queries/index.rq
one that I started editing in part one and then editing the corresponding display template to take advantage of the new parts of the query. I gradually worked the query in queries/index.rq
up to the following. It asks for artist names and their works that have “Flash” in their list of tags:
PREFIX rt: <https://artbase.rhizome.org/prop/direct/>
SELECT DISTINCT ?artistName ?artist ?searchTag WHERE {
BIND("Flash" AS ?searchTag)
?artwork rt:P29 ?artist .
?artist rdfs:label ?artistName .
?artwork rt:P48 ?artbaseLegacyTags .
# Compare lower-case versions of both to make it case-insensitive
FILTER CONTAINS(LCASE(?artbaseLegacyTags),LCASE(?searchTag))
}
ORDER BY (?artistName)
A few notes about this query:
- An artwork’s
rt:P48
value is a comma-delimited list of tags that have been assigned to it. (Some artworks in the dataset do not have tags assigned, so because this triple pattern is not optional, this query would not retrieve any of those.) - I learned about which properties (such as
rt:P48
) do what mostly through exploratory queries and guesswork. Visiting URLs like https://artbase.rhizome.org/wiki/Property:P48 would then show me how good my guesses were. - I could have just put
"Flash"
as the second parameter toCONTAINS()
in theFILTER
line instead of storing it in a?searchTag
variable and referencing that. Storing it in a variable at the top of the query made it easier to change to other values to look for other kinds of works, as we’ll see below.
In the followup to the query revision above, the new version of the template/index.html
display template shown below has three new things:
- A slightly different title.
- The artist name in an
h2
subhead element with the search value (for example, “Flash”) appended. - A Snowman
include
function to insert more content. It names an HTML template to format the inserted content, a query to generate values for the new template, and a parameter to pass to the query: the?artist
value (a URL) retrieved by the main query above.
{{ template "base" . }}
{{ define "title" }}Rhizome Artbase Artists and Works {{ end }}
{{ define "content" }}
<h1>Rhizome Artbase Artists and Works</h1>
<ul>
{{ range . }}
<h2>Artist: {{ .artistName }} ({{ .searchTag }} and other works)</h2>
{{ include "artistsWorks.html" (query "artistsWorks.rq" .artist.String) }}
{{ end }}
</ul>
{{ end }}
Below is the queries/artistsWorks.rq
query referenced by the include
function above. The artist
value passed to it by the template above is plugged in using the <{{.}}>
construct, which I believe is Go template syntax. (I tried to learn more about it, but It’s difficult to do web searches for strings like that. You can see another demonstration of it in Snowman’s inline-queries
example project.)
PREFIX rt: <https://artbase.rhizome.org/prop/direct/>
SELECT DISTINCT ?workTitle ?creationDate ?artworkPage ?artbaseLegacyTags WHERE {
# r:Q676 is the artist Andy Cox if I need to sub it in next line for testing
?artwork rt:P29 <{{.}}> ; # artwork by artist
rdfs:label ?workTitle;
rt:P26 ?creationDateTime .
OPTIONAL { ?artwork rt:P48 ?artbaseLegacyTags . }
# Don't need full ISO date value; just yyyy-mm-dd
BIND(SUBSTR(str(?creationDateTime),1,10) AS ?creationDate)
?artworkPage schema:about ?artwork;
schema:isPartOf <https://artbase.rhizome.org/>.
}
ORDER BY (?workTitle)
I left the qname for one of the artists in a comment near the top of the query because I sometimes replaced the <{{.}}>
with that qname when working out other parts of the query logic.
Remember that the include
function mentioned both this new queries/artistsWorks.rq
file and the template file that goes with it to format the result: template/artistsWorks.html
.
<table>
<tr><th width="200">title</th><th width="100">creation date</th><th>tags</th></tr>
{{ range . }}
<tr>
<td><a href='{{ .artworkPage }}'>{{ .workTitle}}</a></td>
<td>{{ .creationDate}}</td>
<td>{{ .artbaseLegacyTags }}</td>
</tr>
{{ end }}
</table>
This creates a table for each artist’s works with a row for each one. The first cell of each row uses the URL stored in the ?artworkPage
value retrieved by the artistsWorks.rq
query to create a link to that page—for example, to this page for one of the retrieved works.
Once the additions and modifications have been made to the files described so far, a snowman build
creates a new site/index.html
file with the work lists under each artist’s name.
Looking more stylish
I added some simple CSS, but first, in the templates/layouts/default.html
file in the project, I took the / out of the following line so that the generated index.html
file would look for style.css
in the same directory:
<link rel="stylesheet" href="/style.css">
There was already a style.css
file in the project’s static
directory. I replaced its contents with the following minimal CSS:
{ font-family: arial,helvetica; font-size:12pt; }
body {
margin: .25in .5in .25in .5in; /* t,r,b,l */
font-family: arial,helvetica;
}
th {
text-align: left;
background: lightgray;
}
Another snowman build
then created a version of the page that looks like the one that I previewed in part one of this series:
Query for 3D works
I mentioned how I stored the string “Flash” in the ?searchTag
variable of the queries/index.rq
query to make it easier to have this query search for artwork tagged with other values. After changing this variable’s value to “3D” and doing another build, the top of the index.html
file looked like this:
The image at the top of this blog entry is from 1+1+1+1+1+1+1+1+1+1+1+1 by by Grégory Chatonsky, one of the works tagged as 3D.
The search for the keyword is just a simple substring search of the CSV list. If a work had been tagged with “3DogNight”, that also would have been retrieved in the search for “3D”. For a more serious search of tags, I would make a copy of the keyword list that was not only all lower-case but also had spaces removed and began and ended with commas “,like,this,”. Then, a search of that for a version of ?searchTag
enclosed by commas such as “,3d,” would be more accurate.
Possible next steps
I zipped up my artbase snowman project and made it available so that you can unzip it in your own Snowman examples
directory and try it out. The “Getting started with Snowman” section of the Snowman home page has brief descriptions of other projects included in that examples
directory as part of the Snowman distribution. Each of those demonstrates other features that you can incorporate into your own Snowman website projects. One included example that is not listed there is nested-lists-with-single-query
, which shows a way to “render nested lists without the need of multiple queries or views”.
You can apply these features to you own copy of my artbase
project, or you can apply them to you own new Snowman projects that you create with snowman new
. Let me know how it turns out!
Comments? Reply to my tweet announcing this blog entry.
Share this post