From c192b49bfd424892e538020c843f00f59eaaf52a Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Wed, 2 Nov 2016 03:19:19 +0100 Subject: [PATCH] core: functions to get the sets of all subjects, predicates, objects and resources of Descriptions and Graphs --- lib/rdf.ex | 21 +++++++++ lib/rdf/description.ex | 68 ++++++++++++++++++++++++++-- lib/rdf/graph.ex | 82 ++++++++++++++++++++++++++++++++++ test/unit/description_test.exs | 5 ++- test/unit/graph_test.exs | 5 ++- test/unit/rdf_test.exs | 3 ++ 6 files changed, 177 insertions(+), 7 deletions(-) diff --git a/lib/rdf.ex b/lib/rdf.ex index 5e3f79f..c98df3e 100644 --- a/lib/rdf.ex +++ b/lib/rdf.ex @@ -100,6 +100,27 @@ defmodule RDF do def bnode(id), do: BlankNode.new(id) + @doc """ + Checks if the given value is a RDF resource. + + ## Examples + + iex> RDF.resource?(RDF.uri("http://example.com/resource")) + true + iex> RDF.resource?(EX.resource) + true + iex> RDF.resource?(RDF.bnode) + true + iex> RDF.resource?(42) + false + """ + def resource?(value) + def resource?(%URI{}), do: true + def resource?(atom) when is_atom(atom), do: resource?(Vocabulary.__uri__(atom)) + def resource?(%BlankNode{}), do: true + def resource?(_), do: false + + ################################################################################ # temporary manual RDF vocab definitions # TODO: These should be defined as a vocabulary diff --git a/lib/rdf/description.ex b/lib/rdf/description.ex index 88580ec..f083402 100644 --- a/lib/rdf/description.ex +++ b/lib/rdf/description.ex @@ -53,17 +53,79 @@ defmodule RDF.Description do end end - @doc """ - Returns the number of statements of the `RDF.Description`. + Returns the number of statements of a `RDF.Description`. """ def count(%RDF.Description{predications: predications}) do Enum.reduce predications, 0, fn ({_, objects}, count) -> count + Enum.count(objects) end end + @doc """ - Checks if the given statement exists within the `RDF.Description`. + The set of all properties used in the predicates within a `RDF.Description`. + + # Examples + + iex> RDF.Description.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.p2, EX.O2}, + ...> {EX.p2, EX.O3}]) |> + ...> RDF.Description.predicates + MapSet.new([EX.p1, EX.p2]) + """ + def predicates(%RDF.Description{predications: predications}), + do: predications |> Map.keys |> MapSet.new + + @doc """ + The set of all resources used in the objects within a `RDF.Description`. + + Note: This function does collect only URIs and BlankNodes, not Literals. + + # Examples + + iex> RDF.Description.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.p2, EX.O2}, + ...> {EX.p3, EX.O2}, + ...> {EX.p4, RDF.bnode(:bnode)}, + ...> {EX.p3, "foo"} + ...> ]) |> RDF.Description.objects + MapSet.new([RDF.uri(EX.O1), RDF.uri(EX.O2), RDF.bnode(:bnode)]) + """ + def objects(%RDF.Description{predications: predications}) do + Enum.reduce predications, MapSet.new, fn ({_, objects}, acc) -> + objects + |> Map.keys + |> Enum.filter(&RDF.resource?/1) + |> MapSet.new + |> MapSet.union(acc) + end + end + + @doc """ + The set of all resources used within a `RDF.Description`. + + # Examples + + iex> RDF.Description.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.p2, EX.O2}, + ...> {EX.p1, EX.O2}, + ...> {EX.p2, RDF.bnode(:bnode)}, + ...> {EX.p3, "foo"} + ...> ]) |> RDF.Description.resources + MapSet.new([RDF.uri(EX.O1), RDF.uri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2, EX.p3]) + """ + def resources(description) do + description + |> objects + |> MapSet.union(predicates(description)) + end + + + @doc """ + Checks if the given statement exists within a `RDF.Description`. """ def include?(description, statement) diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index 616829d..8ad8b17 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -48,6 +48,88 @@ defmodule RDF.Graph do end end + @doc """ + The set of all properties used in the predicates within a `RDF.Graph`. + + # Examples + + iex> RDF.Graph.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.S2, EX.p2, EX.O2}, + ...> {EX.S1, EX.p2, EX.O3}]) |> + ...> RDF.Graph.subjects + MapSet.new([RDF.uri(EX.S1), RDF.uri(EX.S2)]) + """ + def subjects(%RDF.Graph{descriptions: descriptions}), + do: descriptions |> Map.keys |> MapSet.new + + @doc """ + The set of all properties used in the predicates within a `RDF.Graph`. + + # Examples + + iex> RDF.Graph.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.S2, EX.p2, EX.O2}, + ...> {EX.S1, EX.p2, EX.O3}]) |> + ...> RDF.Graph.predicates + MapSet.new([EX.p1, EX.p2]) + """ + def predicates(%RDF.Graph{descriptions: descriptions}) do + Enum.reduce descriptions, MapSet.new, fn ({_, description}, acc) -> + description + |> Description.predicates + |> MapSet.union(acc) + end + end + + @doc """ + The set of all resources used in the objects within a `RDF.Graph`. + + Note: This function does collect only URIs and BlankNodes, not Literals. + + # Examples + + iex> RDF.Graph.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.S2, EX.p2, EX.O2}, + ...> {EX.S3, EX.p1, EX.O2}, + ...> {EX.S4, EX.p2, RDF.bnode(:bnode)}, + ...> {EX.S5, EX.p3, "foo"} + ...> ]) |> RDF.Graph.objects + MapSet.new([RDF.uri(EX.O1), RDF.uri(EX.O2), RDF.bnode(:bnode)]) + """ + def objects(%RDF.Graph{descriptions: descriptions}) do + Enum.reduce descriptions, MapSet.new, fn ({_, description}, acc) -> + description + |> Description.objects + |> MapSet.union(acc) + end + end + + @doc """ + The set of all resources used within a `RDF.Graph`. + + # Examples + + iex> RDF.Graph.new([ + ...> {EX.S1, EX.p1, EX.O1}, + ...> {EX.S2, EX.p1, EX.O2}, + ...> {EX.S2, EX.p2, RDF.bnode(:bnode)}, + ...> {EX.S3, EX.p1, "foo"} + ...> ]) |> RDF.Graph.resources + MapSet.new([RDF.uri(EX.S1), RDF.uri(EX.S2), RDF.uri(EX.S3), + RDF.uri(EX.O1), RDF.uri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2]) + """ + def resources(graph = %RDF.Graph{descriptions: descriptions}) do + Enum.reduce(descriptions, MapSet.new, fn ({_, description}, acc) -> + description + |> Description.resources + |> MapSet.union(acc) + end) |> MapSet.union(subjects(graph)) + end + + def include?(%RDF.Graph{descriptions: descriptions}, triple = {subject, _, _}) do with triple_subject = Triple.convert_subject(subject), diff --git a/test/unit/description_test.exs b/test/unit/description_test.exs index 0746fd0..5700dab 100644 --- a/test/unit/description_test.exs +++ b/test/unit/description_test.exs @@ -1,13 +1,14 @@ defmodule RDF.DescriptionTest do use ExUnit.Case + defmodule EX, do: + use RDF.Vocabulary, base_uri: "http://example.com/description/" + doctest RDF.Description alias RDF.Description import RDF, only: [uri: 1, literal: 1, bnode: 1] - defmodule EX, do: - use RDF.Vocabulary, base_uri: "http://example.com/description/" def description, do: Description.new(EX.Subject) def description_of_subject(%Description{subject: subject}, subject), do: true diff --git a/test/unit/graph_test.exs b/test/unit/graph_test.exs index cede311..2693d8c 100644 --- a/test/unit/graph_test.exs +++ b/test/unit/graph_test.exs @@ -1,13 +1,14 @@ defmodule RDF.GraphTest do use ExUnit.Case + defmodule EX, do: + use RDF.Vocabulary, base_uri: "http://example.com/graph/" + doctest RDF.Graph alias RDF.Graph import RDF, only: [uri: 1] - defmodule EX, do: - use RDF.Vocabulary, base_uri: "http://example.com/graph/" def graph, do: unnamed_graph def unnamed_graph, do: Graph.new diff --git a/test/unit/rdf_test.exs b/test/unit/rdf_test.exs index 82f75cd..a8edf0e 100644 --- a/test/unit/rdf_test.exs +++ b/test/unit/rdf_test.exs @@ -1,5 +1,8 @@ defmodule RDF.CoreTest do use ExUnit.Case + + defmodule EX, do: use RDF.Vocabulary, base_uri: "http://example.com/" + doctest RDF # alias RDF.{Triple, Literal, BlankNode}