/
quad.ex
172 lines (134 loc) · 6.45 KB
/
quad.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
defmodule RDF.Quad do
@moduledoc """
Helper functions for RDF quads.
A RDF Quad is represented as a plain Elixir tuple consisting of four valid
RDF values for subject, predicate, object and a graph name.
"""
alias RDF.{Statement, PropertyMap}
@type t :: {
Statement.subject(),
Statement.predicate(),
Statement.object(),
Statement.graph_name()
}
@type t_values :: {String.t(), String.t(), any, String.t()}
@doc """
Creates a `RDF.Quad` with proper RDF values.
An error is raised when the given elements are not coercible to RDF values.
Note: The `RDF.quad` function is a shortcut to this function.
## Examples
iex> RDF.Quad.new("http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph")
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
iex> RDF.Quad.new(EX.S, EX.p, 42, EX.Graph)
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
iex> RDF.Quad.new(EX.S, :p, 42, EX.Graph, RDF.PropertyMap.new(p: EX.p))
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
"""
@spec new(
Statement.coercible_subject(),
Statement.coercible_predicate(),
Statement.coercible_object(),
Statement.coercible_graph_name(),
PropertyMap.t() | nil
) :: t
def new(subject, predicate, object, graph_name, property_map \\ nil)
def new(subject, predicate, object, graph_name, nil) do
{
Statement.coerce_subject(subject),
Statement.coerce_predicate(predicate),
Statement.coerce_object(object),
Statement.coerce_graph_name(graph_name)
}
end
def new(subject, predicate, object, graph_name, %PropertyMap{} = property_map) do
{
Statement.coerce_subject(subject),
Statement.coerce_predicate(predicate, property_map),
Statement.coerce_object(object),
Statement.coerce_graph_name(graph_name)
}
end
@doc """
Creates a `RDF.Quad` with proper RDF values.
An error is raised when the given elements are not coercible to RDF values.
Note: The `RDF.quad` function is a shortcut to this function.
## Examples
iex> RDF.Quad.new {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
{~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
iex> RDF.Quad.new {EX.S, EX.p, 42, EX.Graph}
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
iex> RDF.Quad.new {EX.S, EX.p, 42}
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), nil}
iex> RDF.Quad.new {EX.S, :p, 42, EX.Graph}, RDF.PropertyMap.new(p: EX.p)
{RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
"""
@spec new(Statement.coercible_t(), PropertyMap.t() | nil) :: t
def new(statement, property_map \\ nil)
def new({subject, predicate, object, graph_name}, property_map) do
new(subject, predicate, object, graph_name, property_map)
end
def new({subject, predicate, object}, property_map) do
new(subject, predicate, object, nil, property_map)
end
@doc """
Returns a tuple of native Elixir values from a `RDF.Quad` of RDF terms.
When a `:context` option is given with a `RDF.PropertyMap`, predicates will
be mapped to the terms defined in the `RDF.PropertyMap`, if present.
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
## Examples
iex> RDF.Quad.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
{"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
...> |> RDF.Quad.values(context: %{p: ~I<http://example.com/p>})
{"http://example.com/S", :p, 42, "http://example.com/Graph"}
"""
@spec values(t, keyword) :: t_values | nil
def values(quad, opts \\ []) do
if property_map = PropertyMap.from_opts(opts) do
map(quad, Statement.default_property_mapping(property_map))
else
map(quad, &Statement.default_term_mapping/1)
end
end
@doc """
Returns a tuple where each element from a `RDF.Quad` is mapped with the given function.
Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
The function `fun` will receive a tuple `{statement_position, rdf_term}` where
`statement_position` is one of the atoms `:subject`, `:predicate`, `:object` or
`:graph_name` while `rdf_term` is the RDF term to be mapped. When the given function
returns `nil` this will be interpreted as an error and will become the overhaul
result of the `map/2` call.
## Examples
iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
...> |> RDF.Quad.map(fn
...> {:object, object} ->
...> RDF.Term.value(object)
...> {:graph_name, graph_name} ->
...> graph_name
...> {_, resource} ->
...> resource |> to_string() |> String.last() |> String.to_atom()
...> end)
{:S, :p, 42, ~I<http://example.com/Graph>}
"""
@spec map(t, Statement.term_mapping()) :: t_values | nil
def map({subject, predicate, object, graph_name}, fun) do
with subject_value when not is_nil(subject_value) <- fun.({:subject, subject}),
predicate_value when not is_nil(predicate_value) <- fun.({:predicate, predicate}),
object_value when not is_nil(object_value) <- fun.({:object, object}),
graph_name_value <- fun.({:graph_name, graph_name}) do
{subject_value, predicate_value, object_value, graph_name_value}
else
_ -> nil
end
end
@doc """
Checks if the given tuple is a valid RDF quad.
The elements of a valid RDF quad must be RDF terms. On the subject
position only IRIs and blank nodes allowed, while on the predicate and graph
name position only IRIs allowed. The object position can be any RDF term.
"""
@spec valid?(t | any) :: boolean
def valid?(tuple)
def valid?({_, _, _, _} = quad), do: Statement.valid?(quad)
def valid?(_), do: false
end