Learn the fundamentals of SETLr by building a complete ETL pipeline from CSV to RDF.
SETLr uses declarative SETL (Semantic Extract, Transform, and Load) workflows described in RDF to transform tabular data into semantic RDF graphs. This tutorial teaches you the core concepts step-by-step.
Create a file named social.csv with this content:
ID,Name,MarriedTo,Knows,DOB
Alice,Alice Smith,Bob,Bob; Charles,1/12/1983
Bob,Bob Smith,Alice,Alice; Charles,3/23/1985
Charles,Charles Brown,,Alice; Bob,12/15/1955
Dave,Dave Jones,,,4/25/1967Create social.setl.ttl with namespace prefixes:
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix void: <http://rdfs.org/ns/void#> .
@prefix setl: <http://purl.org/twc/vocab/setl/> .
@prefix csvw: <http://www.w3.org/ns/csvw#> .
@prefix pv: <http://purl.org/net/provenance/ns#> .
@prefix : <http://example.com/setl/> .Add an Extract activity to load the CSV:
:table a csvw:Table, setl:Table ;
csvw:delimiter "," ;
prov:wasGeneratedBy [
a setl:Extract ;
prov:used <social.csv> ;
] .Key Points:
csvw:Tableindicates CSV formatsetl:Tablemarks it as a SETL table resourcecsvw:delimiterspecifies the delimiter (default is comma)csvw:skipRowscan skip header rows if needed
| Type | Format | Options |
|---|---|---|
csvw:Table, setl:Table |
CSV/TSV | csvw:delimiter, csvw:skipRows |
setl:Excel, setl:Table |
Excel (XLS/XLSX) | None |
setl:XPORT, setl:Table |
SAS XPORT | None |
setl:SAS7BDAT, setl:Table |
SAS Dataset | None |
void:Dataset |
RDF (Turtle, JSON-LD, etc.) | None |
owl:Ontology |
OWL Ontology | None |
JSLDT (JSON-LD Templates) transform tables into RDF using Jinja2 templating:
<http://example.com/social> a void:Dataset ;
prov:wasGeneratedBy [
a setl:Transform, setl:JSLDT ;
prov:used :table ;
setl:hasContext '''{
"foaf": "http://xmlns.com/foaf/0.1/"
}''' ;
prov:value '''[{
"@id": "https://example.com/social/{{row.ID}}",
"@type": "foaf:Person",
"foaf:name": "{{row.Name}}"
}]''' ;
] .This generates RDF for each row:
<https://example.com/social/Alice> a foaf:Person ;
foaf:name "Alice Smith" .
<https://example.com/social/Bob> a foaf:Person ;
foaf:name "Bob Smith" .
# ... etcInside JSLDT templates, you have access to:
row- Current row as pandas.Seriestable- Full table as pandas.DataFramename- Row indexisempty()- Function to check for empty/NaN valueshash()- Generate UUIDsre- Python regex moduleresources- All generated SETL resources
Use @if to conditionally include elements:
prov:value '''[{
"@id": "https://example.com/social/{{row.ID}}",
"@type": "foaf:Person",
"foaf:name": "{{row.Name}}",
"http://schema.org/spouse": [{
"@if": "not isempty(row.MarriedTo)",
"@id": "https://example.com/social/{{row.MarriedTo}}"
}]
}]''' ;Now only Alice and Bob have schema:spouse properties.
Key Points:
@ifvalue is a Python expression- Wrap in array
[{...}]for valid JSON-LD - Use
isempty()to safely check for NaN/None
Split delimited values with @for:
prov:value '''[{
"@id": "https://example.com/social/{{row.ID}}",
"@type": "foaf:Person",
"foaf:name": "{{row.Name}}",
"foaf:knows": [{
"@if": "not isempty(row.Knows)",
"@for": "friend in row.Knows.split('; ')",
"@do": { "@id": "https://example.com/social/{{friend}}" }
}]
}]''' ;This creates multiple foaf:knows links:
<https://example.com/social/Alice> a foaf:Person ;
foaf:knows <https://example.com/social/Bob>,
<https://example.com/social/Charles> ;
foaf:name "Alice Smith" .Key Points:
@foriterates over Python iterable@dois repeated for each item- Variable (e.g.,
friend) is scoped to the loop
Save to a file:
<social.ttl> a pv:File ;
dcterms:format "text/turtle" ;
prov:wasGeneratedBy [
a setl:Load ;
prov:used <http://example.com/social> ;
] .- RDF/XML:
application/rdf+xml,text/rdf(default) - Turtle:
text/turtle,application/turtle - N-Triples:
text/plain - N3:
text/n3 - TriG:
application/trig - JSON-LD:
application/json
@prefix sd: <http://www.w3.org/ns/sparql-service-description#> .
:sparql_load a setl:Load, sd:Service ;
sd:endpoint <http://localhost:3030/dataset/update> ;
prov:used <http://example.com/social> .Here's the full social.setl.ttl:
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix void: <http://rdfs.org/ns/void#> .
@prefix setl: <http://purl.org/twc/vocab/setl/> .
@prefix csvw: <http://www.w3.org/ns/csvw#> .
@prefix pv: <http://purl.org/net/provenance/ns#> .
@prefix : <http://example.com/setl/> .
# Extract
:table a csvw:Table, setl:Table ;
csvw:delimiter "," ;
prov:wasGeneratedBy [
a setl:Extract ;
prov:used <social.csv> ;
] .
# Transform
<http://example.com/social> a void:Dataset ;
prov:wasGeneratedBy [
a setl:Transform, setl:JSLDT ;
prov:used :table ;
setl:hasContext '''{
"foaf": "http://xmlns.com/foaf/0.1/",
"schema": "http://schema.org/"
}''' ;
prov:value '''[{
"@id": "https://example.com/social/{{row.ID}}",
"@type": "foaf:Person",
"foaf:name": "{{row.Name}}",
"schema:spouse": [{
"@if": "not isempty(row.MarriedTo)",
"@id": "https://example.com/social/{{row.MarriedTo}}"
}],
"foaf:knows": [{
"@if": "not isempty(row.Knows)",
"@for": "friend in row.Knows.split('; ')",
"@do": { "@id": "https://example.com/social/{{friend}}" }
}]
}]''' ;
] .
# Load
<social.ttl> a pv:File ;
dcterms:format "text/turtle" ;
prov:wasGeneratedBy [
a setl:Load ;
prov:used <http://example.com/social> ;
] .setlr social.setl.ttlThis creates social.ttl with the RDF output.
from rdflib import Graph, URIRef
import setlr
# Load script
setl_graph = Graph()
setl_graph.parse("social.setl.ttl", format="turtle")
# Execute
resources = setlr.run_setl(setl_graph)
# Access results
social_graph = resources[URIRef('http://example.com/social')]
print(f"Generated {len(social_graph)} triples")- Learn more about JSLDT Template Language
- Explore Advanced Features:
- See more Examples
- Check the Python API Reference