• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rdf-elixir / rdf-ex / e13c6dddfa3dcff42f19061d70f6a1c7c0caafb8-PR-17

pending completion
e13c6dddfa3dcff42f19061d70f6a1c7c0caafb8-PR-17

Pull #17

github

marcelotto
Pull Request #17: Add implementation of the RDF Dataset canonicalization algorithm

166 of 166 new or added lines in 9 files covered. (100.0%)

5349 of 7224 relevant lines covered (74.04%)

567.51 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

62.5
/lib/rdf/model/quad.ex
1
defmodule RDF.Quad do
2
  @moduledoc """
3
  Helper functions for RDF quads.
4

5
  An RDF Quad is represented as a plain Elixir tuple consisting of four valid
6
  RDF values for subject, predicate, object and a graph name.
7
  """
8

9
  alias RDF.{Statement, BlankNode, PropertyMap}
10

11
  @type t :: {
12
          Statement.subject(),
13
          Statement.predicate(),
14
          Statement.object(),
15
          Statement.graph_name()
16
        }
17

18
  @type coercible ::
19
          {
20
            Statement.coercible_subject(),
21
            Statement.coercible_predicate(),
22
            Statement.coercible_object(),
23
            Statement.coercible_graph_name()
24
          }
25

26
  @type mapping_value :: {String.t(), String.t(), any, String.t()}
27
  # deprecated: This will be removed in v0.11.
28
  @type t_values :: mapping_value
29

30
  @doc """
31
  Creates a `RDF.Quad` with proper RDF values.
32

33
  An error is raised when the given elements are not coercible to RDF values.
34

35
  Note: The `RDF.quad` function is a shortcut to this function.
36

37
  ## Examples
38

39
      iex> RDF.Quad.new("http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph")
40
      {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
41

42
      iex> RDF.Quad.new(EX.S, EX.p, 42, EX.Graph)
43
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
44

45
      iex> RDF.Quad.new(EX.S, :p, 42, EX.Graph, RDF.PropertyMap.new(p: EX.p))
46
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
47
  """
48
  @spec new(
49
          Statement.coercible_subject(),
50
          Statement.coercible_predicate(),
51
          Statement.coercible_object(),
52
          Statement.coercible_graph_name(),
53
          PropertyMap.t() | nil
54
        ) :: t
55
  def new(subject, predicate, object, graph_name, property_map \\ nil)
2✔
56

57
  def new(subject, predicate, object, graph_name, nil) do
58
    {
7,628✔
59
      Statement.coerce_subject(subject),
60
      Statement.coerce_predicate(predicate),
61
      Statement.coerce_object(object),
62
      Statement.coerce_graph_name(graph_name)
63
    }
64
  end
65

66
  def new(subject, predicate, object, graph_name, %PropertyMap{} = property_map) do
67
    {
3✔
68
      Statement.coerce_subject(subject),
69
      Statement.coerce_predicate(predicate, property_map),
70
      Statement.coerce_object(object),
71
      Statement.coerce_graph_name(graph_name)
72
    }
73
  end
74

75
  @doc """
76
  Creates a `RDF.Quad` with proper RDF values.
77

78
  An error is raised when the given elements are not coercible to RDF values.
79

80
  Note: The `RDF.quad` function is a shortcut to this function.
81

82
  ## Examples
83

84
      iex> RDF.Quad.new {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
85
      {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
86

87
      iex> RDF.Quad.new {EX.S, EX.p, 42, EX.Graph}
88
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
89

90
      iex> RDF.Quad.new {EX.S, EX.p, 42}
91
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), nil}
92

93
      iex> RDF.Quad.new {EX.S, :p, 42, EX.Graph}, RDF.PropertyMap.new(p: EX.p)
94
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42), RDF.iri("http://example.com/Graph")}
95
  """
96
  @spec new(Statement.coercible(), PropertyMap.t() | nil) :: t
97
  def new(statement, property_map \\ nil)
7,624✔
98

99
  def new({subject, predicate, object, graph_name}, property_map) do
100
    new(subject, predicate, object, graph_name, property_map)
71✔
101
  end
102

103
  def new({subject, predicate, object}, property_map) do
104
    new(subject, predicate, object, nil, property_map)
7,557✔
105
  end
106

107
  def has_bnode?({%BlankNode{}, _, _, _}), do: true
9✔
108
  def has_bnode?({_, %BlankNode{}, _, _}), do: true
×
109
  def has_bnode?({_, _, %BlankNode{}, _}), do: true
2✔
110
  def has_bnode?({_, _, _, %BlankNode{}}), do: true
×
111
  def has_bnode?({_, _, _, _}), do: false
1✔
112

113
  @doc """
114
  Returns a list of all `RDF.BlankNode`s within the given `quad`.
115
  """
116
  @spec bnodes(t) :: list(BlankNode.t())
117
  def bnodes(quad)
118
  def bnodes({%BlankNode{} = b, _, %BlankNode{} = b, %BlankNode{} = b}), do: [b]
1✔
119
  def bnodes({%BlankNode{} = s, _, %BlankNode{} = b, %BlankNode{} = b}), do: [s, b]
×
120
  def bnodes({%BlankNode{} = b, _, %BlankNode{} = o, %BlankNode{} = b}), do: [b, o]
×
121
  def bnodes({%BlankNode{} = b, _, %BlankNode{} = b, %BlankNode{} = g}), do: [b, g]
×
122
  def bnodes({%BlankNode{} = s, _, %BlankNode{} = o, %BlankNode{} = g}), do: [s, o, g]
7✔
123
  def bnodes({%BlankNode{} = b, _, %BlankNode{} = b, _}), do: [b]
×
124
  def bnodes({%BlankNode{} = s, _, %BlankNode{} = o, _}), do: [s, o]
×
125
  def bnodes({%BlankNode{} = b, _, _, %BlankNode{} = b}), do: [b]
×
126
  def bnodes({%BlankNode{} = s, _, _, %BlankNode{} = g}), do: [s, g]
2✔
127
  def bnodes({_, _, %BlankNode{} = b, %BlankNode{} = b}), do: [b]
×
128
  def bnodes({_, _, %BlankNode{} = o, %BlankNode{} = g}), do: [o, g]
2✔
129
  def bnodes({%BlankNode{} = s, _, _, _}), do: [s]
1✔
130
  def bnodes({_, _, %BlankNode{} = o, _}), do: [o]
×
131
  def bnodes(_), do: []
2✔
132

133
  def include_value?({value, _, _, _}, value), do: true
×
134
  def include_value?({_, value, _, _}, value), do: true
×
135
  def include_value?({_, _, value, _}, value), do: true
×
136
  def include_value?({_, _, _, value}, value), do: true
×
137
  def include_value?({_, _, _, _}), do: false
×
138

139
  @doc """
140
  Returns a tuple of native Elixir values from a `RDF.Quad` of RDF terms.
141

142
  When a `:context` option is given with a `RDF.PropertyMap`, predicates will
143
  be mapped to the terms defined in the `RDF.PropertyMap`, if present.
144

145
  Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
146

147
  ## Examples
148

149
      iex> RDF.Quad.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
150
      {"http://example.com/S", "http://example.com/p", 42, "http://example.com/Graph"}
151

152
      iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
153
      ...> |> RDF.Quad.values(context: %{p: ~I<http://example.com/p>})
154
      {"http://example.com/S", :p, 42,  "http://example.com/Graph"}
155

156
  """
157
  @spec values(t, keyword) :: mapping_value | nil
158
  def values(quad, opts \\ []) do
159
    if property_map = PropertyMap.from_opts(opts) do
9✔
160
      map(quad, Statement.default_property_mapping(property_map))
4✔
161
    else
162
      map(quad, &Statement.default_term_mapping/1)
5✔
163
    end
164
  end
165

166
  @doc """
167
  Returns a tuple where each element from a `RDF.Quad` is mapped with the given function.
168

169
  Returns `nil` if one of the components of the given tuple is not convertible via `RDF.Term.value/1`.
170

171
  The function `fun` will receive a tuple `{statement_position, rdf_term}` where
172
  `statement_position` is one of the atoms `:subject`, `:predicate`, `:object` or
173
  `:graph_name` while `rdf_term` is the RDF term to be mapped. When the given function
174
  returns `nil` this will be interpreted as an error and will become the overhaul
175
  result of the `map/2` call.
176

177
  ## Examples
178

179
      iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42), ~I<http://example.com/Graph>}
180
      ...> |> RDF.Quad.map(fn
181
      ...>      {:object, object} ->
182
      ...>        RDF.Term.value(object)
183
      ...>      {:graph_name, graph_name} ->
184
      ...>        graph_name
185
      ...>      {_, resource} ->
186
      ...>        resource |> to_string() |> String.last() |> String.to_atom()
187
      ...>    end)
188
      {:S, :p, 42, ~I<http://example.com/Graph>}
189

190
  """
191
  @spec map(t, Statement.term_mapping()) :: mapping_value | nil
192
  def map({subject, predicate, object, graph_name}, fun) do
193
    with subject_value when not is_nil(subject_value) <- fun.({:subject, subject}),
7,644✔
194
         predicate_value when not is_nil(predicate_value) <- fun.({:predicate, predicate}),
7,642✔
195
         object_value when not is_nil(object_value) <- fun.({:object, object}),
7,642✔
196
         graph_name_value <- fun.({:graph_name, graph_name}) do
7,642✔
197
      {subject_value, predicate_value, object_value, graph_name_value}
7,642✔
198
    else
199
      _ -> nil
200
    end
201
  end
202

203
  @doc """
204
  Checks if the given tuple is a valid RDF quad.
205

206
  The elements of a valid RDF quad must be RDF terms. On the subject
207
  position only IRIs and blank nodes allowed, while on the predicate and graph
208
  name position only IRIs allowed. The object position can be any RDF term.
209
  """
210
  @spec valid?(t | any) :: boolean
211
  def valid?(tuple)
212
  def valid?({_, _, _, _} = quad), do: Statement.valid?(quad)
13✔
213
  def valid?(_), do: false
20✔
214
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc