hereon-mb/elm-rdf/2.0.0

Rdf.Decode

Extract Elm values from Graph's and Dataset's. This works similar to Json.Decode with a few differences:

  • You have to specify the node at which decoding starts. The reason is, that there is no obvious choice for this within a graph, like it is for a JSON object where decoding always starts at the root.

  • RDF allows multiple objects for one predicate, unlike JSON where a field is assumed to appear once. Therefore one has to use many to switch from decoding exactly one value to allowing arbitrarily many values at a certain predicate.

  • When extracting values from an RDF Dataset, you have to select the correct (named/default) graph(s).

Basic Decoders

type alias Decoder a =
Decoder GraphIdentifier Error a

A way to specify what kind of type you want to decode into.

iri : Decoder Iri

Decode an IRI.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> a <#Person> .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property a iri)
    )
    graph
--> Ok (Rdf.iri "http://example.org/#Person")
blankNode : Decoder a -> Decoder a

Decode a blank node.

literal : Decoder Literal

Decode a literal of any datatype.

blankNodeOrIri : Decoder BlankNodeOrIri

Decode a term which is either a blank node or an IRI.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> <#knows> <bob> .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#knows") blankNodeOrIri)
    )
    graph
--> Ok (Rdf.asBlankNodeOrIri (Rdf.iri "http://example.org/bob"))
blankNodeOrIriOrLiteral : Decoder BlankNodeOrIriOrLiteral

Decode a term which is either a blank node, an IRI, or a literal.

Specific literals

string : Decoder String

Decode a Literal of type xsd:string into a String.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> <#name> "Alice Wonderland" .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#name") string)
    )
    graph
--> Ok "Alice Wonderland"
langString : Decoder ( String, String )

Decode a literal of type rdf:langString into a ( String, String ) where the first element is the language tag and the second the String value.

bool : Decoder Bool

Decode a Literal of type xsd:boolean into a Bool.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> <#isAdmin> true .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#isAdmin") bool)
    )
    graph
--> Ok True
decimal : Decoder Decimal

Decode a literal of type xsd:decimal into a Decimal.

int : Decoder Int

Decode a Literal of type xsd:integer or xsd:int into a Int.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <question> <#hasAnswer> 42 .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/question")
        (property (Rdf.iri "http://example.org/#hasAnswer") int)
    )
    graph
--> Ok 42
float : Decoder Float

Decode a Literal of type xsd:double or xsd:float into a Float.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <question> <#hasAnswer> 314E-2 .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/question")
        (property (Rdf.iri "http://example.org/#hasAnswer") float)
    )
    graph
--> Ok 3.14
number : Decoder Float

Decode a Literal of type xsd:integer, xsd:int, xsd:double, or xsd:float into a Float.

date : Decoder Posix

Decode a Literal of type xsd:date into a Time.Posix.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)
import Time


graph : Graph
graph =
    """
    @base <http://example.org/> .
    @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    <alice> <#birthDate> "1970-01-01"^^xsd:date .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#birthDate") date)
    )
    graph
--> Ok (Time.millisToPosix 0)
dateTime : Decoder Posix

Decode a Literal of type xsd:date into a Time.Posix.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)
import Time


graph : Graph
graph =
    """
    @base <http://example.org/> .
    @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    <alice> <#birthTime> "1970-01-01T00:00:00"^^xsd:dateTime .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#birthTime") dateTime)
    )
    graph
--> Ok (Time.millisToPosix 0)

Focus nodes

from : IsBlankNodeOrIri compatible -> Decoder a -> Decoder a

Start decoding from the provided (focus) node. You can also nest this Decoder in other Decoder's if you have to change the focus node.

fromSubject : Decoder a -> Decoder a

Start decoding from all subjects. If the graph contains more then one triple, you have to combine this with many to get a result.

at : List BlankNodeOrIriOrLiteral -> Decoder a -> Decoder a

Start decoding at each of the provided (focus) node. If more then one of these nodes exist in your graph, you have to combine this with many to get a result.

Datasets

= DefaultGraph
| NamedGraph BlankNodeOrIri

This type is used to distinguish the different graphs an RDF dataset is composed of.

inGraphs : List GraphIdentifier -> Decoder a -> Decoder a

When decoding a Dataset, run the decoder only inside the graphs specified by the list of GraphIdentifier's.

inAllGraphs : Decoder a -> Decoder a

When decoding a Dataset, run the decoder inside the union of all graphs (default and named).

Multiplicities

many : Decoder a -> Decoder (List a)

Decode a list of focus nodes by the given decoder.

Note that the position of many within a decode expression matters. Say, a focus node has multiple classes. The following decoder works as expected:

property a (many class)

However, the following (type-valid) decoder does not work:

many (property a class)

The reason is that property a class fails (since there are many classes), and so many (property a class) fails as a whole!

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> a <#Person> , <#Author> .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property a (many iri))
    )
    graph
--> Ok
-->   [ Rdf.iri "http://example.org/#Person"
-->   , Rdf.iri "http://example.org/#Author"
-->   ]
indexedMany : (Int -> Decoder a) -> Decoder (List a)

This is like many but you also get the index of the currently decoded value. Note that the order in which nodes are decoded is non-deterministic.

Inconsistent structure

oneOf : List (Decoder a) -> Decoder a

Try a bunch of different Decoder's. It will succeed with the first one which succeeded. When all fail, you get the error for all cases.

Containers

list : Decoder a -> Decoder (List a)

Decode an RDF collection into a List.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> <#knows> (
        <bob>
        <cindi>
    ) .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#knows") (list iri))
    )
    graph
--> Ok [ Rdf.iri "http://example.org/bob", Rdf.iri "http://example.org/cindi" ]


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property (Rdf.iri "http://example.org/#knows") (many (list iri)))
    )
    graph
--> Ok [ [ Rdf.iri "http://example.org/bob", Rdf.iri "http://example.org/cindi" ] ]

Properties

property : IsPath compatible -> Decoder a -> Decoder a

Run a decoder at the node which is obtained by following the provided property path. Make sure to use many if more then one node can be found, otherwise decoding will fail.

anyPredicate : Decoder a -> Decoder a

Run a decoder on the nodes which can be reached by any predicate from the current focus node. Make sure to use many if more then one node can be reached in this way.

noProperty : IsPath compatible -> Decoder ()

This decoder succeeds if no nodes can be reached by following the provided property path.

predicates : Decoder (List Iri)

Decode the list of all predicates for which the current focus node is the subject.

Running Decoders

Use Rdf.Graph.decode or Rdf.Dataset.decode to run a decoder on a Graph or Dataset.

type alias Error =
{ error : Problem
, contextStack : List String
}

When the decoding fails, we get on of these. Use errorToString to turn this into a human-friendly form.

type Problem
= NoFocusNode
| ExpectedNode BlankNodeOrIri
| ExpectedIri BlankNodeOrIriOrLiteral
| ExpectedBlankNode BlankNodeOrIriOrLiteral
| ExpectedLiteral BlankNodeOrIriOrLiteral
| ExpectedLiteralOf (List Iri) Literal
| ExpectedBlankNodeOrIri BlankNodeOrIriOrLiteral
| ExpectedOneNode (List BlankNodeOrIriOrLiteral)
| ExpectedPath BlankNodeOrIri Path
| ProblemAt (List BlankNodeOrIri) Path Problem
| ExpectedNoPath
BlankNodeOrIri
Path
(List BlankNodeOrIriOrLiteral)
| ExpectedPredicates BlankNodeOrIri
| ExpectedOneOf (List Problem)
| Custom String
| CustomAt String (List BlankNodeOrIriOrLiteral)

The problem which made decoding fail.

errorToString : Error -> String

Turn a decoding error into a human friendly string.

inContext : String -> Decoder a -> Decoder a

You can use this to mark that you are in a certain context. A stack of these contexts will be provided with the Problem when decoding fails, which you then can use to improve the reported error message.

Transforming and chaining

succeed : a -> Decoder a

A decoder which always succeeds with the given value.

fail : String -> Decoder a

A decoder which always fails with the given message.

map : (a -> b) -> Decoder a -> Decoder b

Transform the decoded value by applying the provided function.

map2 : (a -> b -> c) -> Decoder a -> Decoder b -> Decoder c

Like map but it takes two Decoder's.

andMap : Decoder a -> Decoder (a -> b) -> Decoder b

A helper for combining Decoder's in a pipeline.

andThen : (a -> Decoder b) -> Decoder a -> Decoder b

Run a Decoder and then run another one, where you can use the already decoded value.

combine : List (Decoder a) -> Decoder (List a)

Turn a list of Decoder's into a Decoder of a list. This decoder will fail whenever on of its elements fails.

lazy : (() -> Decoder a) -> Decoder a

Use this if you need to decode recursive data structures.

Pipelines

required : IsPath compatible -> Decoder a -> Decoder (a -> b) -> Decoder b

Decode a required property.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)
import Time


graph : Graph
graph =
    """
    @base <http://example.org/> .
    @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    <alice> <#knows> [ <#name> "Bob Builder" ] .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


type alias Person =
    { name : String
    }


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (succeed Person
            |> required
                (Rdf.sequence
                    (Rdf.iri "http://example.org/#knows")
                    [ Rdf.iri "http://example.org/#name" ]
                )
                string
        )
    )
    graph
--> Ok
-->     { name = "Bob Builder"
-->     }
optional : IsPath compatible -> Decoder a -> a -> Decoder (a -> b) -> Decoder b

Decode an optional property.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)
import Time


graph : Graph
graph =
    """
    @base <http://example.org/> .
    @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
    <alice> <#name> "Alice Wonderland" .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


type alias Person =
    { name : String
    }


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (succeed Person
            |> optional
                (Rdf.sequence
                    (Rdf.iri "http://example.org/#knows")
                    [ Rdf.iri "http://example.org/#name" ]
                )
                string
                "Unknown"
        )
    )
    graph
--> Ok
-->     { name = "Unknown"
-->     }
hardcoded : a -> Decoder (a -> b) -> Decoder b

Rather than decoding anything, use a fixed value for the next step in the pipeline. harcoded does not look at the RDF graph at all.

import Rdf exposing (a)
import Rdf.Graph as Graph exposing (Graph)


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> <#name> "Alice Wonderland" .
    """
        |> Graph.fromTurtle Rdf.initialSeed
        |> Result.map Tuple.first
        |> Result.withDefault Graph.empty


type alias Person =
    { name : String
    , admin : Bool
    }


Graph.decode
    (from
        (Rdf.iri "http://example.org/alice")
        (succeed Person
            |> required (Rdf.iri "http://example.org/#name") string
            |> hardcoded True
        )
    )
    graph
--> Ok
-->     { name = "Alice Wonderland"
-->     , admin = True
-->     }
custom : Decoder a -> Decoder (a -> b) -> Decoder b

Run the given decoder and feed its result into the pipeline at this point.