Read an RDF graph — N-Triples, N-Quads or Turtle — as data.
This is the lexer; vaelii.foreign.rdf is the translation, and the split is the
same one cycl and cyc make. Nothing here knows what rdfs:subClassOf means.
Three syntaxes, one reader. They are the same grammar at three levels of sugar, so separating them would mean three parsers that disagree about IRIs:
| syntax | what it adds |
|---|---|
| N-Triples | <s> <p> <o> . — one statement per line, no abbreviation of any kind |
| N-Quads | a fourth term, the graph the statement belongs to |
| Turtle | @prefix, @base, a, ;, ,, […], (…), numeric and boolean literals |
A bulk dump ships as N-Triples or N-Quads (Wikidata, YAGO, DBpedia) because a line-per-statement file streams and shards; a hand-written ontology ships as Turtle (schema.org, BFO, most of the OBO Foundry's OWL) because a person has to read it. A reader that could only take the first would be a reader you cannot point at an ontology, and one that could only take the second would not scale to a real dump.
How a term comes back. As in cycl, the namespace of a symbol is what tells
two kinds apart that would otherwise collide:
| RDF | Clojure |
|---|---|
<http://example.org/Dog> | iri/http://example.org/Dog |
ex:Dog (prefix expanded) | iri/http://example.org/Dog |
_:b0 | bnode/b0 |
"text" | "text" |
42, 1.5, true | 42, 1.5, true |
"dog"@en | {:lex "dog" :lang "en"} |
"2020-01-01"^^xsd:date | {:lex "2020-01-01" :datatype iri/…} |
A literal is a plain Clojure value whenever it has nothing else to say — a
language tag or a datatype vaelii has no native reading of is what makes it a map
instead. That keeps the common case cheap and the tagged case honest: a Wikidata
dump's labels differ only by @lang, and a reader that dropped the tag would make
fifty translations of one label indistinguishable.
Blank nodes are expanded, not deferred. [ :p :o ] and ( a b c ) are
abbreviations for triples about a fresh blank node, so the reader emits those triples
and hands back the node — which is why one statement can yield many, and why the seq
is over triples rather than over lines.
The reader is a lazy seq over a Reader, so a multi-gigabyte dump streams in constant
memory.
Read an RDF graph — N-Triples, N-Quads or Turtle — as data.
This is the *lexer*; `vaelii.foreign.rdf` is the translation, and the split is the
same one `cycl` and `cyc` make. Nothing here knows what `rdfs:subClassOf` means.
**Three syntaxes, one reader.** They are the same grammar at three levels of sugar,
so separating them would mean three parsers that disagree about IRIs:
| syntax | what it adds |
|------------|------------------------------------------------------------------|
| N-Triples | `<s> <p> <o> .` — one statement per line, no abbreviation of any kind |
| N-Quads | a fourth term, the **graph** the statement belongs to |
| Turtle | `@prefix`, `@base`, `a`, `;`, `,`, `[…]`, `(…)`, numeric and boolean literals |
A bulk dump ships as N-Triples or N-Quads (Wikidata, YAGO, DBpedia) because a
line-per-statement file streams and shards; a hand-written ontology ships as Turtle
(schema.org, BFO, most of the OBO Foundry's OWL) because a person has to read it. A
reader that could only take the first would be a reader you cannot point at an
ontology, and one that could only take the second would not scale to a real dump.
**How a term comes back.** As in `cycl`, the *namespace* of a symbol is what tells
two kinds apart that would otherwise collide:
| RDF | Clojure |
|----------------------------|--------------------------------|
| `<http://example.org/Dog>` | `iri/http://example.org/Dog` |
| `ex:Dog` (prefix expanded) | `iri/http://example.org/Dog` |
| `_:b0` | `bnode/b0` |
| `"text"` | `"text"` |
| `42`, `1.5`, `true` | `42`, `1.5`, `true` |
| `"dog"@en` | `{:lex "dog" :lang "en"}` |
| `"2020-01-01"^^xsd:date` | `{:lex "2020-01-01" :datatype iri/…}` |
A literal is a **plain Clojure value** whenever it has nothing else to say — a
language tag or a datatype vaelii has no native reading of is what makes it a map
instead. That keeps the common case cheap and the tagged case honest: a Wikidata
dump's labels differ only by `@lang`, and a reader that dropped the tag would make
fifty translations of one label indistinguishable.
**Blank nodes are expanded, not deferred.** `[ :p :o ]` and `( a b c )` are
abbreviations for triples about a fresh blank node, so the reader emits those triples
and hands back the node — which is why one statement can yield many, and why the seq
is over *triples* rather than over lines.
The reader is a lazy seq over a `Reader`, so a multi-gigabyte dump streams in constant
memory.(bnode? x)Is x a blank node? One written _:b in the source, or one this reader minted to
hold a […] or (…) abbreviation together.
Is `x` a blank node? One written `_:b` in the source, or one this reader minted to hold a `[…]` or `(…)` abbreviation together.
(iri-str x)The IRI text of an iri/… symbol.
The IRI text of an `iri/…` symbol.
(iri? x)Is x an IRI — the iri/… symbol form?
Is `x` an IRI — the `iri/…` symbol form?
(lex x)The lexical form of any literal, tagged or plain.
The lexical form of any literal, tagged or plain.
(literal lexical datatype lang)A literal from its lexical form plus an optional datatype IRI and language tag. Untagged, and tagged with a datatype we read natively, come back as plain values; a language tag always survives, since it is the only thing telling two translations of one string apart.
Public because it is the contract, not a helper: vaelii.foreign.rdfxml is a second
lexer over the same graph, and two lexers that normalized literals differently would
hand vaelii.foreign.rdf two different datasets for one ontology depending on which
syntax it happened to be published in. A datatype and a language tag together is not
a thing RDF has — pass one or the other.
A literal from its lexical form plus an optional datatype IRI and language tag. Untagged, and tagged with a datatype we read natively, come back as plain values; a language tag always survives, since it is the only thing telling two translations of one string apart. Public because it is the *contract*, not a helper: `vaelii.foreign.rdfxml` is a second lexer over the same graph, and two lexers that normalized literals differently would hand `vaelii.foreign.rdf` two different datasets for one ontology depending on which syntax it happened to be published in. A datatype and a language tag together is not a thing RDF has — pass one or the other.
The RDF namespace. Public because the syntax's own vocabulary is spelled from it in
three places — here, in vaelii.foreign.rdfxml, and in vaelii.foreign.rdf — and two
spellings of rdf:type would be two terms.
The RDF namespace. Public because the syntax's own vocabulary is spelled from it in three places — here, in `vaelii.foreign.rdfxml`, and in `vaelii.foreign.rdf` — and two spellings of `rdf:type` would be two terms.
(resolve-iri base ref)ref resolved against base, by RFC 3986 §5.3.
This is the whole algorithm, dot segments included, and it is worth the fifty lines:
xml:base in an OWL file is routinely http://example.org/dir/file with ../other
hanging off it, an ontology's import closure is written in relative references, and a
resolver that is merely close produces IRIs that look right and name nothing. The W3C
syntax suites test exactly these cases, which is how the earlier approximation here was
found to be wrong.
`ref` resolved against `base`, by RFC 3986 §5.3. This is the whole algorithm, dot segments included, and it is worth the fifty lines: `xml:base` in an OWL file is routinely `http://example.org/dir/file` with `../other` hanging off it, an ontology's import closure is written in relative references, and a resolver that is merely close produces IRIs that look right and name nothing. The W3C syntax suites test exactly these cases, which is how the earlier approximation here was found to be wrong.
(tagged? x)Is x a literal carrying a language tag or a datatype — the map form?
Is `x` a literal carrying a language tag or a datatype — the map form?
(triples rdr)(triples rdr opts)Every triple rdr holds, as a lazy seq of {:s :p :o} maps — with :g on a quad.
The reader is left open: the caller owns it (with-open), because the seq is consumed
lazily.
opts may carry :base, for a fragment of a document whose header is elsewhere.
:strict? true makes a malformed statement throw rather than be skipped. The default
is to skip, because that is what a bulk dump needs; malformed! says why both readings
have to exist.
Every triple `rdr` holds, as a lazy seq of `{:s :p :o}` maps — with `:g` on a quad.
The reader is left open: the caller owns it (`with-open`), because the seq is consumed
lazily.
`opts` may carry `:base`, for a fragment of a document whose header is elsewhere.
`:strict? true` makes a malformed statement throw rather than be skipped. The default
is to skip, because that is what a bulk dump needs; `malformed!` says why both readings
have to exist.(with-triples path f)(with-triples path f opts)Open the RDF file at path, call (f triples) on a lazy seq of its triples, and
close the file after. A .gz path is decompressed on the way through, since that is
how every dump worth streaming ships. The seq is only valid inside f — it reads
from the open stream.
Open the RDF file at `path`, call `(f triples)` on a lazy seq of its triples, and close the file after. A `.gz` path is decompressed on the way through, since that is how every dump worth streaming ships. The seq is only valid inside `f` — it reads from the open stream.
The XML Schema namespace, which every datatype RDF has a native reading of lives in.
Public because vaelii.foreign.rdf tests a range against it — an rdfs:range of
xsd:string constrains a value rather than naming a type of term.
The XML Schema namespace, which every datatype RDF has a native reading of lives in. Public because `vaelii.foreign.rdf` tests a range against it — an `rdfs:range` of `xsd:string` constrains a value rather than naming a type of term.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |