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

rdf-elixir / jsonld-ex / de3ca9b1dae3a8dffda3dec6d69d62a58ee3622e

10 Apr 2025 03:31PM UTC coverage: 91.542%. Remained the same
de3ca9b1dae3a8dffda3dec6d69d62a58ee3622e

push

github

marcelotto
Remove unused HTML versions of test manifests

1775 of 1939 relevant lines covered (91.54%)

4387.12 hits per line

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

89.47
/lib/json/ld/flattening.ex
1
defmodule JSON.LD.Flattening do
2
  @moduledoc """
3
  Implementation of the JSON-LD 1.1 Flattening Algorithms.
4

5
  <https://www.w3.org/TR/json-ld11-api/#flattening-algorithms>
6
  """
7

8
  import JSON.LD.{NodeIdentifierMap, Utils}
9

10
  alias JSON.LD.{NodeIdentifierMap, Options}
11

12
  def flatten(input, options \\ %Options{}) do
13
    node_map = node_map(input)
508✔
14

15
    {default_graph, named_graphs} = Map.pop(node_map, "@default")
504✔
16

17
    graph_maps =
504✔
18
      named_graphs
19
      |> maybe_sort_by(options.ordered, fn {graph_name, _} -> graph_name end)
504✔
20
      |> Enum.reduce(default_graph, fn {graph_name, graph}, graph_maps ->
21
        entry =
40✔
22
          if Map.has_key?(graph_maps, graph_name) do
23
            graph_maps[graph_name]
32✔
24
          else
25
            %{"@id" => graph_name}
8✔
26
          end
27

28
        graph =
40✔
29
          Enum.reject(graph, fn {_, node} ->
30
            Map.has_key?(node, "@id") and map_size(node) == 1
116✔
31
          end)
32

33
        graph_entry =
40✔
34
          graph
35
          |> maybe_sort_by(options.ordered, fn {id, _node} -> id end)
40✔
36
          |> Enum.map(fn {_id, node} -> node end)
96✔
37

38
        Map.put(
40✔
39
          graph_maps,
40
          graph_name,
41
          Map.update(entry, "@graph", graph_entry, fn existing -> existing ++ graph_entry end)
×
42
        )
43
      end)
44

45
    graph_maps
46
    |> maybe_sort_by(options.ordered, fn {id, _node} -> id end)
504✔
47
    |> Enum.flat_map(fn {_, node} ->
504✔
48
      if Enum.count(node) == 1 and Map.has_key?(node, "@id") do
1,228✔
49
        []
50
      else
51
        [node]
52
      end
53
    end)
54
  end
55

56
  @spec node_map([map], pid | nil) :: map
57
  def node_map(input, node_id_map \\ nil)
508✔
58

59
  def node_map(input, nil) do
60
    {:ok, node_id_map} = NodeIdentifierMap.start_link()
508✔
61

62
    try do
508✔
63
      node_map(input, node_id_map)
508✔
64
    after
65
      NodeIdentifierMap.stop(node_id_map)
508✔
66
    end
67
  end
68

69
  def node_map(input, node_id_map) do
70
    generate_node_map(input, %{"@default" => %{}}, node_id_map)
2,196✔
71
  end
72

73
  @doc """
74
  Node Map Generation
75

76
  <https://www.w3.org/TR/json-ld11-api/#node-map-generation>
77
  """
78
  @spec generate_node_map(
79
          [map] | map,
80
          map,
81
          pid,
82
          String.t(),
83
          String.t() | nil,
84
          String.t() | nil,
85
          pid | nil
86
        ) :: map
87
  def generate_node_map(
88
        element,
89
        node_map,
90
        node_id_map,
91
        active_graph \\ "@default",
2,196✔
92
        active_subject \\ nil,
476✔
93
        active_property \\ nil,
×
94
        list \\ nil
5,944✔
95
      )
96

97
  # 1)
98
  def generate_node_map(
99
        element,
100
        node_map,
101
        node_id_map,
102
        active_graph,
103
        active_subject,
104
        active_property,
105
        list
106
      )
107
      when is_list(element) do
108
    Enum.reduce(element, node_map, fn item, node_map ->
8,924✔
109
      generate_node_map(
10,864✔
110
        item,
111
        node_map,
112
        node_id_map,
113
        active_graph,
114
        active_subject,
115
        active_property,
116
        list
117
      )
118
    end)
119
  end
120

121
  # 2)
122
  def generate_node_map(
123
        element,
124
        node_map,
125
        node_id_map,
126
        active_graph,
127
        active_subject,
128
        active_property,
129
        list
130
      )
131
      when is_map(element) do
132
    node_map = Map.put_new(node_map, active_graph, %{})
11,016✔
133
    node = node_map[active_graph][active_subject]
11,016✔
134

135
    # 3)
136
    element =
11,016✔
137
      if old_types = Map.get(element, "@type") do
138
        new_types =
1,036✔
139
          Enum.map(List.wrap(old_types), fn item ->
140
            if blank_node_id?(item) do
1,188✔
141
              generate_blank_node_id(node_id_map, item)
20✔
142
            else
143
              item
1,168✔
144
            end
145
          end)
146

147
        Map.put(
1,036✔
148
          element,
149
          "@type",
150
          if(is_list(old_types), do: new_types, else: List.first(new_types))
1,036✔
151
        )
152
      else
153
        element
9,980✔
154
      end
155

156
    cond do
11,016✔
157
      # 4)
158
      Map.has_key?(element, "@value") ->
159
        if is_nil(list) do
4,108✔
160
          if node do
3,864✔
161
            update_in(node_map, [active_graph, active_subject, active_property], fn
3,864✔
162
              nil -> [element]
×
163
              items -> if element not in items, do: items ++ [element], else: items
3,864✔
164
            end)
165
          else
166
            node_map
×
167
          end
168
        else
169
          append_to_list(list, element)
244✔
170
          node_map
244✔
171
        end
172

173
      # 5)
174
      Map.has_key?(element, "@list") ->
6,908✔
175
        {:ok, result_list} = new_list()
460✔
176

177
        {node_map, result} =
460✔
178
          try do
460✔
179
            {
180
              generate_node_map(
181
                element["@list"],
182
                node_map,
183
                node_id_map,
184
                active_graph,
185
                active_subject,
186
                active_property,
187
                result_list
188
              ),
189
              get_list(result_list)
190
            }
191
          after
192
            terminate_list(result_list)
460✔
193
          end
194

195
        if is_nil(list) do
460✔
196
          if node do
352✔
197
            update_in(node_map, [active_graph, active_subject, active_property], fn
352✔
198
              nil -> [result]
×
199
              items -> items ++ [result]
352✔
200
            end)
201
          else
202
            node_map
×
203
          end
204
        else
205
          append_to_list(list, result)
108✔
206
          node_map
108✔
207
        end
208

209
      # 6)
210
      true ->
6,448✔
211
        # 6.1)
212
        {id, element} = Map.pop(element, "@id")
6,448✔
213

214
        id =
6,448✔
215
          if id do
216
            if blank_node_id?(id), do: generate_blank_node_id(node_id_map, id), else: id
4,388✔
217

218
            # 6.2)
219
          else
220
            generate_blank_node_id(node_id_map)
2,060✔
221
          end
222

223
        # 6.3)
224
        node_map =
6,448✔
225
          if not Map.has_key?(node_map[active_graph], id) do
226
            Map.update!(node_map, active_graph, fn graph ->
6,052✔
227
              Map.put_new(graph, id, %{"@id" => id})
6,052✔
228
            end)
229
          else
230
            node_map
396✔
231
          end
232

233
        # 6.4)
234
        node = node_map[active_graph][id]
6,448✔
235

236
        # 6.5)
237
        node_map =
6,448✔
238
          if is_map(active_subject) do
239
            if not Map.has_key?(node, active_property) do
152✔
240
              update_in(node_map, [active_graph, id, active_property], fn
152✔
241
                nil ->
152✔
242
                  [active_subject]
243

244
                items ->
245
                  if active_subject not in items, do: items ++ [active_subject], else: items
×
246
              end)
247
            else
248
              node_map
×
249
            end
250

251
            # 6.6)
252
          else
253
            if not is_nil(active_property) do
6,296✔
254
              reference = %{"@id" => id}
2,696✔
255

256
              if is_nil(list) do
2,696✔
257
                update_in(node_map, [active_graph, active_subject, active_property], fn
2,504✔
258
                  nil -> [reference]
×
259
                  items -> if reference not in items, do: items ++ [reference], else: items
2,504✔
260
                end)
261
              else
262
                # 6.6.3)
263
                append_to_list(list, reference)
192✔
264
                node_map
192✔
265
              end
266
            else
267
              node_map
3,600✔
268
            end
269
          end
270

271
        # 6.7)
272
        {node_map, element} =
6,448✔
273
          if Map.has_key?(element, "@type") do
6,448✔
274
            node_map =
628✔
275
              Enum.reduce(element["@type"], node_map, fn type, node_map ->
276
                update_in(node_map, [active_graph, id, "@type"], fn
780✔
277
                  nil -> [type]
592✔
278
                  items -> if type not in items, do: items ++ [type], else: items
188✔
279
                end)
280
              end)
281

282
            {node_map, Map.delete(element, "@type")}
283
          else
284
            {node_map, element}
285
          end
286

287
        # 6.8)
288
        {node_map, element} =
6,448✔
289
          if Map.has_key?(element, "@index") do
6,444✔
290
            {element_index, element} = Map.pop(element, "@index")
148✔
291

292
            node_map =
148✔
293
              if node_index = get_in(node_map, [active_graph, id, "@index"]) do
294
                if not deep_compare(node_index, element_index) do
4✔
295
                  raise JSON.LD.Error.conflicting_indexes(node_index, element_index)
4✔
296
                end
297
              else
298
                update_in(node_map, [active_graph, id], fn node ->
144✔
299
                  Map.put(node, "@index", element_index)
144✔
300
                end)
301
              end
302

303
            {node_map, element}
304
          else
305
            {node_map, element}
306
          end
307

308
        # 6.9)
309
        {node_map, element} =
6,444✔
310
          if Map.has_key?(element, "@reverse") do
6,444✔
311
            referenced_node = %{"@id" => id}
84✔
312
            {reverse_map, element} = Map.pop(element, "@reverse")
84✔
313

314
            node_map =
84✔
315
              Enum.reduce(reverse_map, node_map, fn {property, values}, node_map ->
316
                Enum.reduce(values, node_map, fn value, node_map ->
96✔
317
                  generate_node_map(
152✔
318
                    value,
319
                    node_map,
320
                    node_id_map,
321
                    active_graph,
322
                    referenced_node,
323
                    property
324
                  )
325
                end)
326
              end)
327

328
            {node_map, element}
329
          else
330
            {node_map, element}
331
          end
332

333
        # 6.10)
334
        {node_map, element} =
6,444✔
335
          if Map.has_key?(element, "@graph") do
6,444✔
336
            {graph, element} = Map.pop(element, "@graph")
348✔
337
            {generate_node_map(graph, node_map, node_id_map, id), element}
338
          else
339
            {node_map, element}
340
          end
341

342
        # 6.11)
343
        {node_map, element} =
6,444✔
344
          if Map.has_key?(element, "@included") do
6,444✔
345
            {included, element} = Map.pop(element, "@included")
128✔
346
            {generate_node_map(included, node_map, node_id_map, active_graph), element}
347
          else
348
            {node_map, element}
349
          end
350

351
        # 6.12)
352
        element
353
        |> Enum.sort_by(fn {property, _} -> property end)
5,792✔
354
        |> Enum.reduce(node_map, fn {property, value}, node_map ->
6,444✔
355
          property =
5,792✔
356
            if blank_node_id?(property) do
357
              generate_blank_node_id(node_id_map, property)
8✔
358
            else
359
              property
5,784✔
360
            end
361

362
          node_map =
5,792✔
363
            if not Map.has_key?(node_map[active_graph][id], property) do
364
              update_in(node_map, [active_graph, id], fn node -> Map.put(node, property, []) end)
5,776✔
365
            else
366
              node_map
16✔
367
            end
368

369
          generate_node_map(value, node_map, node_id_map, active_graph, id, property)
5,792✔
370
        end)
371
    end
372
  end
373

374
  @spec deep_compare(map | [map], map | [map]) :: boolean
375
  defp deep_compare(v1, v2) when is_map(v1) and is_map(v2) do
376
    Enum.count(v1) == Enum.count(v2) &&
×
377
      Enum.all?(v1, fn {k, v} ->
×
378
        Map.has_key?(v2, k) && deep_compare(v, v2[k])
×
379
      end)
380
  end
381

382
  defp deep_compare(v1, v2) when is_list(v1) and is_list(v2) do
383
    Enum.count(v1) == Enum.count(v2) && MapSet.new(v1) == MapSet.new(v2)
×
384
  end
385

386
  defp deep_compare(v, v), do: true
×
387
  defp deep_compare(_, _), do: false
4✔
388

389
  @spec new_list :: Agent.on_start()
390
  defp new_list do
391
    Agent.start_link(fn -> %{"@list" => []} end)
460✔
392
  end
393

394
  @spec terminate_list(pid) :: :ok
395
  defp terminate_list(pid) do
396
    :ok = Agent.stop(pid)
460✔
397
  end
398

399
  @spec get_list(pid) :: map
400
  defp get_list(pid) do
401
    Agent.get(pid, fn list_node -> list_node end)
460✔
402
  end
403

404
  @spec append_to_list(pid, map) :: :ok
405
  defp append_to_list(pid, element) do
406
    Agent.update(pid, fn list_node ->
544✔
407
      Map.update(list_node, "@list", [element], fn list -> list ++ [element] end)
544✔
408
    end)
409
  end
410
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