hereon-mb/elm-rdf/1.1.2

NOTE: the latest version is 2.0.0

Rdf.Graph.Decode

Extract Elm values from Rdf.Graph'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 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.

Basic Decoders

type Decoder a

A way to specify what kind of thing 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.parse
        |> Result.withDefault Graph.empty


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

Decode a literal of any datatype.

blankNodeOrIri : Decoder BlankNodeOrIri

Decode a Blank Node or IRI.

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


graph : Graph
graph =
    """
    @base <http://example.org/> .
    <alice> <#knows> <bob> .
    """
        |> Graph.parse
        |> Result.withDefault Graph.empty


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 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.parse
        |> Result.withDefault Graph.empty


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.parse
        |> Result.withDefault Graph.empty


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.parse
        |> Result.withDefault Graph.empty


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.parse
        |> Result.withDefault Graph.empty


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.parse
        |> Result.withDefault Graph.empty


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

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.parse
        |> Result.withDefault Graph.empty


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.

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.

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)

Note that property a class alone 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.parse
        |> Result.withDefault Graph.empty


decode
    (from
        (Rdf.iri "http://example.org/alice")
        (property a (many iri))
    )
    graph
--> Ok
-->   [ Rdf.iri "http://example.org/#Author"
-->   , Rdf.iri "http://example.org/#Person"
-->   ]
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 collections 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.parse
        |> Result.withDefault Graph.empty


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" ]


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.

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

decode : Decoder a -> Graph -> Result Error a

Run a Decoder on an actual Graph starting at the provided Node's.

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.parse
        |> Result.withDefault Graph.empty


type alias Person =
    { name : String
    }


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.parse
        |> Result.withDefault Graph.empty


type alias Person =
    { name : String
    }


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.parse
        |> Result.withDefault Graph.empty


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


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.