• 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

82.14
/lib/rdf/model/triple.ex
1
defmodule RDF.Triple do
2
  @moduledoc """
3
  Helper functions for RDF triples.
4

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

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

11
  @type t :: {Statement.subject(), Statement.predicate(), Statement.object()}
12

13
  @type coercible ::
14
          {
15
            Statement.coercible_subject(),
16
            Statement.coercible_predicate(),
17
            Statement.coercible_object()
18
          }
19

20
  @type mapping_value :: {String.t(), String.t(), any}
21
  # deprecated: This will be removed in v0.11.
22
  @type t_values :: mapping_value
23

24
  @doc """
25
  Creates a `RDF.Triple` with proper RDF values.
26

27
  An error is raised when the given elements are not coercible to RDF values.
28

29
  Note: The `RDF.triple` function is a shortcut to this function.
30

31
  ## Examples
32

33
      iex> RDF.Triple.new("http://example.com/S", "http://example.com/p", 42)
34
      {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
35

36
      iex> RDF.Triple.new(EX.S, EX.p, 42)
37
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
38

39
      iex> RDF.Triple.new(EX.S, :p, 42, RDF.PropertyMap.new(p: EX.p))
40
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
41
  """
42
  @spec new(
43
          Statement.coercible_subject(),
44
          Statement.coercible_predicate(),
45
          Statement.coercible_object(),
46
          PropertyMap.t() | nil
47
        ) :: t
48
  def new(subject, predicate, object, property_map \\ nil)
2✔
49

50
  def new(subject, predicate, object, nil) do
51
    {
7✔
52
      Statement.coerce_subject(subject),
53
      Statement.coerce_predicate(predicate),
54
      Statement.coerce_object(object)
55
    }
56
  end
57

58
  def new(subject, predicate, object, %PropertyMap{} = property_map) do
59
    {
2✔
60
      Statement.coerce_subject(subject),
61
      Statement.coerce_predicate(predicate, property_map),
62
      Statement.coerce_object(object)
63
    }
64
  end
65

66
  @doc """
67
  Creates a `RDF.Triple` with proper RDF values.
68

69
  An error is raised when the given elements are not coercible to RDF values.
70

71
  Note: The `RDF.triple` function is a shortcut to this function.
72

73
  ## Examples
74

75
      iex> RDF.Triple.new {"http://example.com/S", "http://example.com/p", 42}
76
      {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
77

78
      iex> RDF.Triple.new {EX.S, EX.p, 42}
79
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
80

81
      iex> RDF.Triple.new {EX.S, EX.p, 42, EX.Graph}
82
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
83

84
      iex> RDF.Triple.new {EX.S, :p, 42}, RDF.PropertyMap.new(p: EX.p)
85
      {RDF.iri("http://example.com/S"), RDF.iri("http://example.com/p"), RDF.literal(42)}
86
  """
87
  @spec new(Statement.coercible(), PropertyMap.t() | nil) :: t
88
  def new(statement, property_map \\ nil)
3✔
89

90
  def new({subject, predicate, object}, property_map),
91
    do: new(subject, predicate, object, property_map)
5✔
92

93
  def new({subject, predicate, object, _}, property_map),
94
    do: new(subject, predicate, object, property_map)
1✔
95

96
  def has_bnode?({%BlankNode{}, _, _}), do: true
255✔
97
  def has_bnode?({_, %BlankNode{}, _}), do: true
×
98
  def has_bnode?({_, _, %BlankNode{}}), do: true
11✔
99
  def has_bnode?({_, _, _}), do: false
59✔
100

101
  @doc """
102
  Returns a list of all `RDF.BlankNode`s within the given `triple`.
103
  """
104
  @spec bnodes(t) :: list(BlankNode.t())
105
  def bnodes(triple)
106
  def bnodes({%BlankNode{} = b, _, %BlankNode{} = b}), do: [b]
4✔
107
  def bnodes({%BlankNode{} = s, _, %BlankNode{} = o}), do: [s, o]
219✔
108
  def bnodes({%BlankNode{} = s, _, _}), do: [s]
34✔
109
  def bnodes({_, _, %BlankNode{} = o}), do: [o]
11✔
110
  def bnodes(_), do: []
60✔
111

112
  def include_value?({value, _, _}, value), do: true
×
113
  def include_value?({_, value, _}, value), do: true
×
114
  def include_value?({_, _, value}, value), do: true
×
115
  def include_value?({_, _, _}), do: false
×
116

117
  @doc """
118
  Returns a tuple of native Elixir values from a `RDF.Triple` of RDF terms.
119

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

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

125
  ## Examples
126

127
      iex> RDF.Triple.values {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
128
      {"http://example.com/S", "http://example.com/p", 42}
129

130
      iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
131
      ...> |> RDF.Triple.values(context: %{p: ~I<http://example.com/p>})
132
      {"http://example.com/S", :p, 42}
133

134
  """
135
  @spec values(t, keyword) :: mapping_value | nil
136
  def values(triple, opts \\ []) do
137
    if property_map = PropertyMap.from_opts(opts) do
10✔
138
      map(triple, Statement.default_property_mapping(property_map))
6✔
139
    else
140
      map(triple, &Statement.default_term_mapping/1)
4✔
141
    end
142
  end
143

144
  @doc """
145
  Returns a triple where each element from a `RDF.Triple` is mapped with the given function.
146

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

149
  The function `fun` will receive a tuple `{statement_position, rdf_term}` where
150
  `statement_position` is one of the atoms `:subject`, `:predicate` or `:object`,
151
  while `rdf_term` is the RDF term to be mapped. When the given function returns
152
  `nil` this will be interpreted as an error and will become the overhaul result
153
  of the `map/2` call.
154

155
  ## Examples
156

157
      iex> {~I<http://example.com/S>, ~I<http://example.com/p>, RDF.literal(42)}
158
      ...> |> RDF.Triple.map(fn
159
      ...>      {:object, object} -> RDF.Term.value(object)
160
      ...>      {_, term}         -> term |> to_string() |> String.last()
161
      ...>    end)
162
      {"S", "p", 42}
163

164
  """
165
  @spec map(t, Statement.term_mapping()) :: mapping_value | nil
166
  def map({subject, predicate, object}, fun) do
167
    with subject_value when not is_nil(subject_value) <- fun.({:subject, subject}),
278✔
168
         predicate_value when not is_nil(predicate_value) <- fun.({:predicate, predicate}),
276✔
169
         object_value when not is_nil(object_value) <- fun.({:object, object}) do
276✔
170
      {subject_value, predicate_value, object_value}
276✔
171
    else
172
      _ -> nil
173
    end
174
  end
175

176
  @doc """
177
  Checks if the given tuple is a valid RDF triple.
178

179
  The elements of a valid RDF triple must be RDF terms. On the subject
180
  position only IRIs and blank nodes allowed, while on the predicate position
181
  only IRIs allowed. The object position can be any RDF term.
182
  """
183
  @spec valid?(t | any) :: boolean
184
  def valid?(tuple)
185
  def valid?({_, _, _} = triple), do: Statement.valid?(triple)
15✔
186
  def valid?(_), do: false
21✔
187
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