From dc7dce7dbca22ad1fa0f2b867dc1b013209dc614 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 23 Jul 2017 23:59:50 +0200 Subject: [PATCH] Add describes?/1 to RDF.Data protocol and all RDF data structures --- CHANGELOG.md | 4 +++- lib/rdf/data.ex | 24 +++++++++++++++++++----- lib/rdf/dataset.ex | 41 +++++++++++++++++++++++++++++++++++++++++ lib/rdf/description.ex | 17 +++++++++++++++++ lib/rdf/graph.ex | 16 ++++++++++++++++ test/unit/data_test.exs | 26 ++++++++++++++++++++++---- 6 files changed, 118 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d69337..4e98616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,10 @@ This project adheres to [Semantic Versioning](http://semver.org/) and ### Added - Turtle decoder +- `describes?/1` on `RDF.Data` protocol and all RDF data structures which check + if statements about a given resource exist - `RDF.Data.descriptions/1` which returns all descriptions within a RDF data structure -- `RDF.Description.first/2` which returns a single object a predicate of a `RDF.Description` +- `RDF.Description.first/2` which returns a single object to a predicate of a `RDF.Description` - `RDF.Description.objects/2` with custom filter function - `RDF.bnode?/1` which checks if the given value is a blank node diff --git a/lib/rdf/data.ex b/lib/rdf/data.ex index 80c6cc3..3527242 100644 --- a/lib/rdf/data.ex +++ b/lib/rdf/data.ex @@ -37,9 +37,9 @@ defprotocol RDF.Data do def include?(data, statements) @doc """ - Returns the list of all statements of a RDF data structure. + Checks if a RDF data structure contains statements about the given resource. """ - def statements(data) + def describes?(data, subject) @doc """ Returns a `RDF.Description` of the given subject. @@ -57,6 +57,11 @@ defprotocol RDF.Data do """ def descriptions(data) + @doc """ + Returns the list of all statements of a RDF data structure. + """ + def statements(data) + @doc """ Returns the set of all resources which are subject of the statements of a RDF data structure. """ @@ -129,7 +134,8 @@ defimpl RDF.Data, for: RDF.Description do def include?(description, statements), do: RDF.Description.include?(description, statements) - def statements(description), do: RDF.Description.statements(description) + def describes?(description, subject), + do: RDF.Description.describes?(description, subject) def description(%RDF.Description{subject: subject} = description, s) do with ^subject <- RDF.Statement.convert_subject(s) do @@ -141,6 +147,8 @@ defimpl RDF.Data, for: RDF.Description do def descriptions(description), do: [description] + def statements(description), do: RDF.Description.statements(description) + def subjects(%RDF.Description{subject: subject}), do: MapSet.new([subject]) def predicates(description), do: RDF.Description.predicates(description) def objects(description), do: RDF.Description.objects(description) @@ -193,13 +201,16 @@ defimpl RDF.Data, for: RDF.Graph do def include?(graph, statements), do: RDF.Graph.include?(graph, statements) - def statements(graph), do: RDF.Graph.statements(graph) + def describes?(graph, subject), + do: RDF.Graph.describes?(graph, subject) def description(graph, subject), do: RDF.Graph.description(graph, subject) || RDF.Description.new(subject) def descriptions(graph), do: RDF.Graph.descriptions(graph) + def statements(graph), do: RDF.Graph.statements(graph) + def subjects(graph), do: RDF.Graph.subjects(graph) def predicates(graph), do: RDF.Graph.predicates(graph) def objects(graph), do: RDF.Graph.objects(graph) @@ -231,7 +242,8 @@ defimpl RDF.Data, for: RDF.Dataset do def include?(dataset, statements), do: RDF.Dataset.include?(dataset, statements) - def statements(dataset), do: RDF.Dataset.statements(dataset) + def describes?(dataset, subject), + do: RDF.Dataset.who_describes(dataset, subject) != [] def description(dataset, subject) do with subject = RDF.Statement.convert_subject(subject) do @@ -250,6 +262,8 @@ defimpl RDF.Data, for: RDF.Dataset do |> Enum.map(&(description(dataset, &1))) end + def statements(dataset), do: RDF.Dataset.statements(dataset) + def subjects(dataset), do: RDF.Dataset.subjects(dataset) def predicates(dataset), do: RDF.Dataset.predicates(dataset) def objects(dataset), do: RDF.Dataset.objects(dataset) diff --git a/lib/rdf/dataset.ex b/lib/rdf/dataset.ex index 39f81e8..6c97a88 100644 --- a/lib/rdf/dataset.ex +++ b/lib/rdf/dataset.ex @@ -683,6 +683,47 @@ defmodule RDF.Dataset do do: include?(dataset, {subject, predicate, object}, graph_context) + @doc """ + Checks if a graph of a `RDF.Dataset` contains statements about the given resource. + + ## Examples + + iex> RDF.Dataset.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Dataset.describes?(EX.S1) + true + iex> RDF.Dataset.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Dataset.describes?(EX.S2) + false + """ + def describes?(%RDF.Dataset{graphs: graphs}, subject, graph_context \\ nil) do + with graph_context = convert_graph_name(graph_context) do + if graph = graphs[graph_context] do + Graph.describes?(graph, subject) + else + false + end + end + end + + @doc """ + Returns the names of all graphs of a `RDF.Dataset` containing statements about the given subject. + + ## Examples + iex> dataset = RDF.Dataset.new([ + ...> {EX.S1, EX.p, EX.O}, + ...> {EX.S2, EX.p, EX.O}, + ...> {EX.S1, EX.p, EX.O, EX.Graph1}, + ...> {EX.S2, EX.p, EX.O, EX.Graph2}]) + ...> RDF.Dataset.who_describes(dataset, EX.S1) + [nil, RDF.uri(EX.Graph1)] + """ + def who_describes(%RDF.Dataset{graphs: graphs}, subject) do + with subject = convert_subject(subject) do + graphs + |> Map.values + |> Stream.filter(&Graph.describes?(&1, subject)) + |> Enum.map(&(&1.name)) + end + end + defimpl Enumerable do def member?(graph, statement), do: {:ok, RDF.Dataset.include?(graph, statement)} def count(graph), do: {:ok, RDF.Dataset.statement_count(graph)} diff --git a/lib/rdf/description.ex b/lib/rdf/description.ex index be8e612..d3c6730 100644 --- a/lib/rdf/description.ex +++ b/lib/rdf/description.ex @@ -552,6 +552,23 @@ defmodule RDF.Description do def include?(%RDF.Description{}, _), do: false + @doc """ + Checks if a `RDF.Description` has the given resource as subject. + + ## Examples + + iex> RDF.Description.new(EX.S1, EX.p1, EX.O1) |> RDF.Description.describes?(EX.S1) + true + iex> RDF.Description.new(EX.S1, EX.p1, EX.O1) |> RDF.Description.describes?(EX.S2) + false + """ + def describes?(%RDF.Description{subject: subject}, other_subject) do + with other_subject = convert_subject(other_subject) do + subject == other_subject + end + end + + defimpl Enumerable do def member?(desc, triple), do: {:ok, RDF.Description.include?(desc, triple)} def count(desc), do: {:ok, RDF.Description.count(desc)} diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index 6a45eac..36bb3d6 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -582,6 +582,22 @@ defmodule RDF.Graph do end end + @doc """ + Checks if a `RDF.Graph` contains statements about the given resource. + + ## Examples + + iex> RDF.Graph.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Graph.describes?(EX.S1) + true + iex> RDF.Graph.new([{EX.S1, EX.p1, EX.O1}]) |> RDF.Graph.describes?(EX.S2) + false + """ + def describes?(%RDF.Graph{descriptions: descriptions}, subject) do + with subject = convert_subject(subject) do + Map.has_key?(descriptions, subject) + end + end + defimpl Enumerable do def member?(desc, triple), do: {:ok, RDF.Graph.include?(desc, triple)} diff --git a/test/unit/data_test.exs b/test/unit/data_test.exs index 2ff677a..c8b198a 100644 --- a/test/unit/data_test.exs +++ b/test/unit/data_test.exs @@ -91,8 +91,9 @@ defmodule RDF.DataTest do refute RDF.Data.include?(description, {EX.Other, EX.p1, EX.O2}) end - test "statements", %{description: description} do - assert RDF.Data.statements(description) == Description.statements(description) + test "describes?", %{description: description} do + assert RDF.Data.describes?(description, EX.S) + refute RDF.Data.describes?(description, EX.Other) end test "description when the requested subject matches the Description.subject", @@ -111,6 +112,10 @@ defmodule RDF.DataTest do assert RDF.Data.descriptions(description) == [description] end + test "statements", %{description: description} do + assert RDF.Data.statements(description) == Description.statements(description) + end + test "subjects", %{description: description} do assert RDF.Data.subjects(description) == MapSet.new([uri(EX.S)]) end @@ -215,8 +220,10 @@ defmodule RDF.DataTest do refute RDF.Data.include?(graph, {EX.Other, EX.p1, EX.O2}) end - test "statements", %{graph: graph} do - assert RDF.Data.statements(graph) == Graph.statements(graph) + test "describes?", %{graph: graph} do + assert RDF.Data.describes?(graph, EX.S) + assert RDF.Data.describes?(graph, EX.S2) + refute RDF.Data.describes?(graph, EX.Other) end test "description when a description is present", @@ -234,6 +241,10 @@ defmodule RDF.DataTest do [description, EX.S2 |> EX.p2(EX.O3, EX.O4)] end + test "statements", %{graph: graph} do + assert RDF.Data.statements(graph) == Graph.statements(graph) + end + test "subjects", %{graph: graph} do assert RDF.Data.subjects(graph) == MapSet.new([uri(EX.S), uri(EX.S2)]) end @@ -321,6 +332,13 @@ defmodule RDF.DataTest do refute RDF.Data.include?(dataset, {EX.Other, EX.p1, EX.O2}) end + test "describes?", %{dataset: dataset} do + assert RDF.Data.describes?(dataset, EX.S) + assert RDF.Data.describes?(dataset, EX.S2) + assert RDF.Data.describes?(dataset, EX.S3) + refute RDF.Data.describes?(dataset, EX.Other) + end + test "description when a description is present", %{dataset: dataset, description: description} do description_aggregate = Description.add(description, {EX.S, EX.p3, EX.O5})