From e141841e78ef82eb71aeb12867d5fdc0a6a8ae0f Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Sun, 12 Mar 2017 14:27:52 +0100 Subject: [PATCH] core: vocabulary namespace rewrite - ability to create vocabulary namespaces from RDF data - XSD, RDF, RDFS, OWL and SKOS vocabulary namespaces --- lib/rdf.ex | 11 +- lib/rdf/exceptions.ex | 8 +- lib/rdf/literal.ex | 61 +++- lib/rdf/namespace.ex | 60 ++++ lib/rdf/ns.ex | 49 +++ lib/rdf/vocabularies/rdfs.ex | 11 - lib/rdf/vocabularies/xsd.ex | 55 --- lib/rdf/vocabulary.ex | 162 --------- lib/rdf/vocabulary_namespace.ex | 256 ++++++++++++++ priv/vocabs/owl.nt | 450 ++++++++++++++++++++++++ priv/vocabs/rdf.nt | 102 ++++++ priv/vocabs/rdfs.nt | 87 +++++ priv/vocabs/skos.nt | 252 +++++++++++++ test/data/vocab_ns_example2.nt | 2 + test/support/rdf_case.ex | 10 +- test/unit/literal_test.exs | 4 +- test/unit/namespace_test.exs | 7 + test/unit/ntriples/reader_test.exs | 13 +- test/unit/rdf_test.exs | 3 +- test/unit/vocabulary_namespace_test.exs | 182 ++++++++++ test/unit/vocabulary_test.exs | 127 ------- 21 files changed, 1536 insertions(+), 376 deletions(-) create mode 100644 lib/rdf/namespace.ex create mode 100644 lib/rdf/ns.ex delete mode 100644 lib/rdf/vocabularies/rdfs.ex delete mode 100644 lib/rdf/vocabularies/xsd.ex delete mode 100644 lib/rdf/vocabulary.ex create mode 100644 lib/rdf/vocabulary_namespace.ex create mode 100644 priv/vocabs/owl.nt create mode 100644 priv/vocabs/rdf.nt create mode 100644 priv/vocabs/rdfs.nt create mode 100644 priv/vocabs/skos.nt create mode 100644 test/data/vocab_ns_example2.nt create mode 100644 test/unit/namespace_test.exs create mode 100644 test/unit/vocabulary_namespace_test.exs delete mode 100644 test/unit/vocabulary_test.exs diff --git a/lib/rdf.ex b/lib/rdf.ex index 5e61112..8e93f11 100644 --- a/lib/rdf.ex +++ b/lib/rdf.ex @@ -1,8 +1,8 @@ defmodule RDF do - alias RDF.{Vocabulary, Literal, BlankNode, Triple} + alias RDF.{Namespace, Literal, BlankNode, Triple} @doc """ - Generator function for URIs from strings or term atoms of a `RDF.Vocabulary`. + Generator function for URIs from strings or term atoms of a `RDF.Namespace`. ## Examples @@ -10,7 +10,7 @@ defmodule RDF do %URI{authority: "www.example.com", fragment: nil, host: "www.example.com", path: "/foo", port: 80, query: nil, scheme: "http", userinfo: nil} - iex> RDF.uri(RDF.RDFS.Class) + iex> RDF.uri(RDF.NS.RDFS.Class) %URI{authority: "www.w3.org", fragment: "Class", host: "www.w3.org", path: "/2000/01/rdf-schema", port: 80, query: nil, scheme: "http", userinfo: nil} @@ -19,7 +19,8 @@ defmodule RDF do ** (RDF.InvalidURIError) string "not a uri" is not a valid URI """ @spec uri(URI.t | binary | atom) :: URI.t - def uri(atom) when is_atom(atom), do: Vocabulary.__uri__(atom) + def uri(atom) when is_atom(atom), do: Namespace.resolve_term(atom) + def uri(string) do parsed_uri = URI.parse(string) if uri?(parsed_uri) do @@ -114,7 +115,7 @@ defmodule RDF do """ def resource?(value) def resource?(%URI{}), do: true - def resource?(atom) when is_atom(atom), do: resource?(Vocabulary.__uri__(atom)) + def resource?(atom) when is_atom(atom), do: resource?(Namespace.resolve_term(atom)) def resource?(%BlankNode{}), do: true def resource?(_), do: false diff --git a/lib/rdf/exceptions.ex b/lib/rdf/exceptions.ex index 58cfd6d..13eae70 100644 --- a/lib/rdf/exceptions.ex +++ b/lib/rdf/exceptions.ex @@ -31,15 +31,11 @@ defmodule RDF.Quad.InvalidGraphContextError do end -defmodule RDF.Vocabulary.InvalidBaseURIError do +defmodule RDF.Namespace.InvalidVocabBaseURIError do defexception [:message] end -defmodule RDF.Vocabulary.UndefinedTermError do - defexception [:message] -end - -defmodule RDF.Vocabulary.InvalidTermError do +defmodule RDF.Namespace.UndefinedTermError do defexception [:message] end diff --git a/lib/rdf/literal.ex b/lib/rdf/literal.ex index 4ac3f5f..2bcd423 100644 --- a/lib/rdf/literal.ex +++ b/lib/rdf/literal.ex @@ -6,7 +6,64 @@ defmodule RDF.Literal do @type t :: module - alias RDF.{XSD, RDFS} + # Since the capability of RDF.Vocabulary.Namespaces requires the compilation + # of the RDF.NTriples.Reader and the RDF.NTriples.Reader depends on RDF.Literals, + # we can't define the XSD namespace in RDF.NS. + defmodule NS do + @moduledoc false + @vocabdoc false + use RDF.Vocabulary.Namespace + defvocab XSD, + base_uri: "http://www.w3.org/2001/XMLSchema#", + terms: ~w[ + string + normalizedString + token + language + Name + NCName + ID + IDREF + IDREFS + ENTITY + ENTITIES + NMTOKEN + NMTOKENS + boolean + float + double + decimal + integer + long + int + short + byte + nonPositiveInteger + negativeInteger + nonNegativeInteger + positiveInteger + unsignedLong + unsignedInt + unsignedShort + unsignedByte + duration + dateTime + time + date + gYearMonth + gYear + gMonthDay + gDay + gMonth + base64Binary + hexBinary + anyURI + QName + NOTATION + ] + end + alias NS.XSD + @doc """ Creates a new `RDF.Literal` of the given value and tries to infer an appropriate XSD datatype. @@ -28,7 +85,7 @@ defmodule RDF.Literal do # Examples iex> RDF.Literal.new(42) - %RDF.Literal{value: 42, language: nil, datatype: RDF.uri(RDF.XSD.integer)} + %RDF.Literal{value: 42, language: nil, datatype: RDF.uri(XSD.integer)} """ def new(value) diff --git a/lib/rdf/namespace.ex b/lib/rdf/namespace.ex new file mode 100644 index 0000000..111806c --- /dev/null +++ b/lib/rdf/namespace.ex @@ -0,0 +1,60 @@ +defmodule RDF.Namespace do + @moduledoc """ + A `RDF.Namespace` is a module ... + + TODO: Rewrite this + + A `RDF.Namespace` is a collection of URIs and serves as a namespace for its + elements, called terms. The terms can be accessed by qualification on the + resp. namespace module. + + ## Using a `RDF.Namespace` + + There are two types of terms in a `RDF.Namespace`, which are resolved + differently: + + 1. Lowercased terms (usually used for RDF properties, but this is not + enforced) are represented as functions on a Vocabulary module and return the + URI directly. + 2. Capitalized terms are by standard Elixir semantics modules names, i.e. + atoms. In all places in RDF.ex, where an URI is expected, you can use atoms + qualified with a `RDF.Namespace` directly, but if you want to resolve it + manually, you can pass the `RDF.Namespace` qualified atom to `RDF.uri`. + + Examples: + + iex> RDF.NS.RDFS.subClassOf + %URI{authority: "www.w3.org", fragment: "subClassOf", host: "www.w3.org", + path: "/2000/01/rdf-schema", port: 80, query: nil, scheme: "http", + userinfo: nil} + iex> RDF.NS.RDFS.Class + RDF.NS.RDFS.Class + iex> RDF.uri(RDF.NS.RDFS.Class) + %URI{authority: "www.w3.org", fragment: "Class", host: "www.w3.org", + path: "/2000/01/rdf-schema", port: 80, query: nil, scheme: "http", + userinfo: nil} + iex> alias RDF.NS.RDFS + iex> RDF.triple(RDFS.Class, RDFS.subClassOf, RDFS.Resource) + {RDF.uri(RDFS.Class), RDF.uri(RDFS.subClassOf), RDF.uri(RDFS.Resource)} + + """ + + @callback __resolve_term__(atom) :: URI.t + + @callback __terms__() :: [atom] + + + def resolve_term(expr) + + def resolve_term(uri = %URI{}), do: uri + + def resolve_term(namespaced_atom) when is_atom(namespaced_atom) do + {term, namespace} = + namespaced_atom + |> Module.split + |> List.pop_at(-1) + {term, namespace} = {String.to_atom(term), Module.concat(namespace)} + namespace.__resolve_term__(term) + end + +end diff --git a/lib/rdf/ns.ex b/lib/rdf/ns.ex new file mode 100644 index 0000000..c7fe948 --- /dev/null +++ b/lib/rdf/ns.ex @@ -0,0 +1,49 @@ +defmodule RDF.NS do + use RDF.Vocabulary.Namespace + + @vocabdoc """ + The XML Schema datatypes vocabulary. + + See + """ + defvocab XSD, + base_uri: "http://www.w3.org/2001/XMLSchema#", + terms: RDF.Literal.NS.XSD.__terms__ + + @vocabdoc """ + The RDF vocabulary. + + See + """ + defvocab RDF, + base_uri: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + file: "rdf.nt" + + @vocabdoc """ + The RDFS vocabulary. + + See + """ + defvocab RDFS, + base_uri: "http://www.w3.org/2000/01/rdf-schema#", + file: "rdfs.nt" + + @vocabdoc """ + The OWL vocabulary. + + See + """ + defvocab OWL, + base_uri: "http://www.w3.org/2002/07/owl#", + file: "owl.nt" + + @vocabdoc """ + The SKOS vocabulary. + + See + """ + defvocab SKOS, + base_uri: "http://www.w3.org/2004/02/skos/core#", + file: "skos.nt" + +end diff --git a/lib/rdf/vocabularies/rdfs.ex b/lib/rdf/vocabularies/rdfs.ex deleted file mode 100644 index c6c166a..0000000 --- a/lib/rdf/vocabularies/rdfs.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule RDF.RDFS do - @moduledoc """ - The RDFS vocabulary. - - See - """ - - # TODO: This should be a strict vocabulary and loaded from a file. - use RDF.Vocabulary, base_uri: "http://www.w3.org/2000/01/rdf-schema#" - -end diff --git a/lib/rdf/vocabularies/xsd.ex b/lib/rdf/vocabularies/xsd.ex deleted file mode 100644 index 5c3d31c..0000000 --- a/lib/rdf/vocabularies/xsd.ex +++ /dev/null @@ -1,55 +0,0 @@ -defmodule RDF.XSD do - @moduledoc """ - The XML Schema datatypes vocabulary. - - See - """ - - # TODO: This should be a strict vocabulary and loaded from a file. - use RDF.Vocabulary, base_uri: "http://www.w3.org/2001/XMLSchema#" - - defuri :string - defuri :normalizedString - defuri :token - defuri :language - defuri :Name - defuri :NCName - defuri :ID - defuri :IDREF - defuri :IDREFS - defuri :ENTITY - defuri :ENTITIES - defuri :NMTOKEN - defuri :NMTOKENS - defuri :boolean - defuri :float - defuri :double - defuri :decimal - defuri :integer - defuri :long - defuri :int - defuri :short - defuri :byte - defuri :nonPositiveInteger - defuri :negativeInteger - defuri :nonNegativeInteger - defuri :positiveInteger - defuri :unsignedLong - defuri :unsignedInt - defuri :unsignedShort - defuri :unsignedByte - defuri :duration - defuri :dateTime - defuri :time - defuri :date - defuri :gYearMonth - defuri :gYear - defuri :gMonthDay - defuri :gDay - defuri :gMonth - defuri :base64Binary - defuri :hexBinary - defuri :anyURI - defuri :QName - defuri :NOTATION -end diff --git a/lib/rdf/vocabulary.ex b/lib/rdf/vocabulary.ex deleted file mode 100644 index 32f97b6..0000000 --- a/lib/rdf/vocabulary.ex +++ /dev/null @@ -1,162 +0,0 @@ -defmodule RDF.Vocabulary do # or RDF.URI.Namespace? - @moduledoc """ - Defines a RDF Vocabulary. - - A `RDF.Vocabulary` is a collection of URIs and serves as a namespace for its - elements, called terms. The terms can be accessed by qualification on the - resp. Vocabulary module. - - ## Using a Vocabulary - - There are two types of terms in a `RDF.Vocabulary`, which are resolved - differently: - - 1. Lowercased terms (usually used for RDF properties, but this is not - enforced) are represented as functions on a Vocabulary module and return the - URI directly. - 2. Uppercased terms are by standard Elixir semantics modules names, i.e. - atoms. In many in RDF.ex, where an URI is expected, you can use atoms - qualified with a `RDF.Vocabulary` directly, but if you want to resolve it - manually, you can pass the `RDF.Vocabulary` qualified atom to `RDF.uri`. - - Examples: - - iex> RDF.RDFS.subClassOf - %URI{authority: "www.w3.org", fragment: "subClassOf", host: "www.w3.org", - path: "/2000/01/rdf-schema", port: 80, query: nil, scheme: "http", - userinfo: nil} - iex> RDF.RDFS.Class - RDF.RDFS.Class - iex> RDF.uri(RDF.RDFS.Class) - %URI{authority: "www.w3.org", fragment: "Class", host: "www.w3.org", - path: "/2000/01/rdf-schema", port: 80, query: nil, scheme: "http", - userinfo: nil} - iex> RDF.triple(RDF.RDFS.Class, RDF.RDFS.subClass, RDF.RDFS.Resource) - {RDF.uri(RDF.RDFS.Class), RDF.uri(RDF.RDFS.subClass), RDF.uri(RDF.RDFS.Resource)} - - - ## Strict vocabularies - - What is a strict vocabulary and why should I use them over non-strict - vocabularies and define all terms ... - - - ## Defining a vocabulary - - There are two basic ways to define a vocabulary: - - 1. You can define all terms manually. - 2. You can load all terms from a specified namespace in a given dataset or - graph. - - Either way, you'll first have to define a new module for your vocabulary: - - defmodule ExampleVocab do - use RDF.Vocabulary, base_uri: "http://www.example.com/ns/" - - # Your term definitions - end - - The `base_uri` argument with the URI prefix of all the terms in the defined - vocabulary is required and expects a valid URI ending with either a `"/"` or - a `"#"`. - - - ## Reflection - - `__base_uri__` and `__terms__` ... - """ - - defmacro __using__(opts) do - quote bind_quoted: [opts: opts], unquote: true do - import unquote(__MODULE__) - - # TODO: @terms should be a MapSet for faster term lookup - Module.register_attribute __MODULE__, :terms, accumulate: true - - @before_compile unquote(__MODULE__) - - @strict Keyword.get(opts, :strict, false) - - with {:ok, base_uri} <- Keyword.fetch(opts, :base_uri), - true <- base_uri |> String.ends_with?(["/", "#"]) do - @base_uri base_uri - else - :error -> - raise RDF.Vocabulary.InvalidBaseURIError, "required base_uri missing" - false -> - raise RDF.Vocabulary.InvalidBaseURIError, - "a base_uri without a trailing '/' or '#' is invalid" - end - - def __base_uri__, do: @base_uri - def __strict__, do: @strict - - unless @strict do - def unquote(:"$handle_undefined_function")(term, args) do - RDF.Vocabulary.term_to_uri(@base_uri, term) - end - end - end - end - - defmacro __before_compile__(_env) do - quote do - def __terms__, do: @terms - - if @strict do - def uri(term) do - if Enum.member?(@terms, term) do - RDF.Vocabulary.term_to_uri(@base_uri, term) - else - raise RDF.Vocabulary.UndefinedTermError, - "undefined term #{term} in strict vocabulary #{__MODULE__}" - end - end - else - def uri(term) do - RDF.Vocabulary.term_to_uri(@base_uri, term) - end - end - end - end - - @doc """ - Defines an URI via a term concatenated to the `base_uri` of the vocabulary - module. - """ - defmacro defuri(term) when is_atom(term) do - quote do - @terms unquote(term) - - if Atom.to_string(unquote(term)) =~ ~r/^\p{Ll}/u do -# TODO: the URI should be built at compile-time - # uri = RDF.Vocabulary.term_to_uri(@base_uri, unquote(term)) - def unquote(term)() do - URI.parse(__base_uri__() <> to_string(unquote(term))) - end - end - end - end - - @doc false - def term_to_uri(base_uri, term) do - URI.parse(base_uri <> to_string(term)) - end - - @doc false - def __uri__(uri = %URI{}), do: uri - def __uri__(namespaced_atom) when is_atom(namespaced_atom) do - case namespaced_atom - |> to_string - |> String.reverse - |> String.split(".", parts: 2) - |> Enum.map(&String.reverse/1) - |> Enum.map(&String.to_existing_atom/1) do - [term, vocabulary] -> vocabulary.uri(term) - _ -> raise RDF.Vocabulary.InvalidTermError, "" - end - - end - -end diff --git a/lib/rdf/vocabulary_namespace.ex b/lib/rdf/vocabulary_namespace.ex new file mode 100644 index 0000000..74c6c7d --- /dev/null +++ b/lib/rdf/vocabulary_namespace.ex @@ -0,0 +1,256 @@ +defmodule RDF.Vocabulary.Namespace do + @moduledoc """ + Defines a RDF Vocabulary as a `RDF.Namespace`. + + + ## Strict vocabularies + + What is a strict vocabulary and why should I use them over non-strict + vocabularies and define all terms ... + + + ## Defining a vocabulary + + There are two basic ways to define a vocabulary: + + 1. You can define all terms manually. + 2. You can load all terms from a specified namespace in a given dataset or + graph. + + Either way, you'll first have to define a new module for your vocabulary: + + defmodule Example do + use RDF.Vocabulary.Namespace + + defvocab EX, + base_uri: "http://www.example.com/ns/", + terms: ~w[Foo bar] + + # Your term definitions + end + + The `base_uri` argument with the URI prefix of all the terms in the defined + vocabulary is required and expects a valid URI ending with either a `"/"` or + a `"#"`. + + + ## Reflection + + `__base_uri__` and `__terms__` ... + + """ + + @vocabs_dir "priv/vocabs" + + defmacro __using__(_opts) do + quote do + import unquote(__MODULE__) + +# Module.register_attribute __MODULE__, :vocabs_acc, accumulate: true +# +# @before_compile unquote(__MODULE__) + end + end + +# defmacro __before_compile__(_env) do +# quote do +# @__vocabs__ normalize_vocabs( +# Module.delete_attribute(__MODULE__, :vocabs_acc), __MODULE__) +# def __all__, do: @__vocabs__ +# end +# end +# +# def normalize_vocabs(vocabs, parent_module) do +# Enum.reduce vocabs, %{}, fn ({name, opts}, vocabs) -> +# Map.put(vocabs, name, normalize_vocab_opts(name, opts, parent_module)) +# end +# end +# +# defp normalize_vocab_opts(name, opts, parent_module) do +# Keyword.put_new(opts, :module, Module.safe_concat([parent_module, name])) +# end + + @doc """ + Defines a `RDF.Namespace` module for a RDF vocabulary. + """ + defmacro defvocab({:__aliases__, _, [name_atom]} = name, opts) do + base_uri = base_uri!(opts) + file = file!(opts) + terms = terms!(opts) + strict = Keyword.get(opts, :strict, true) + case_separated_terms = group_terms_by_case(terms) + lowercased_terms = Map.get(case_separated_terms, :lowercased, []) + capitalized_terms = Map.get(case_separated_terms, :capitalized, []) + + quote do +# @vocabs_acc {unquote(name_atom), unquote(opts)} + + vocabdoc = Module.delete_attribute(__MODULE__, :vocabdoc) + + defmodule unquote(name) do + @moduledoc vocabdoc + + @behaviour RDF.Namespace + + if unquote(file) do + @external_resource unquote(file) + end + + @base_uri unquote(base_uri) + def __base_uri__, do: @base_uri + + @strict unquote(strict) + def __strict__, do: @strict + + @lowercased_terms unquote(lowercased_terms |> Enum.map(&String.to_atom/1)) + @capitalized_terms unquote(capitalized_terms |> Enum.map(&String.to_atom/1)) + @terms @lowercased_terms ++ @capitalized_terms + def __terms__, do: @terms + + define_vocab_terms unquote(lowercased_terms), unquote(base_uri) + + if @strict do + def __resolve_term__(term) do + if Enum.member?(@capitalized_terms, term) do + term_to_uri(@base_uri, term) + else + raise RDF.Namespace.UndefinedTermError, + "undefined term #{term} in strict vocabulary #{__MODULE__}" + end + end + else + def __resolve_term__(term) do + term_to_uri(@base_uri, term) + end + + def unquote(:"$handle_undefined_function")(term, args) do + term_to_uri(@base_uri, term) + end + end + + Module.delete_attribute(__MODULE__, :tmp_uri) + end + end + end + + defp base_uri!(opts) do + base_uri = Keyword.fetch!(opts, :base_uri) + unless String.ends_with?(base_uri, ["/", "#"]) do + raise RDF.Namespace.InvalidVocabBaseURIError, + "a base_uri without a trailing '/' or '#' is invalid" + else + base_uri + end + end + + def terms!(opts) do + cond do + Keyword.has_key?(opts, :file) -> + opts + |> Keyword.delete(:file) + |> Keyword.put(:data, load_file(file!(opts))) + |> terms! + data = Keyword.get(opts, :data) -> + # TODO: support also RDF.Datasets ... + data = unless match?(%RDF.Graph{}, data) do + # TODO: find an alternative to Code.eval_quoted + {data, _ } = Code.eval_quoted(data, [], data_env()) + data + else + data + end + data_vocab_terms(data, Keyword.fetch!(opts, :base_uri)) + terms = Keyword.get(opts, :terms) -> + # TODO: find an alternative to Code.eval_quoted - We want to support that the terms can be given as sigils ... + {terms, _ } = Code.eval_quoted(terms, [], data_env()) + terms + |> Enum.map(&to_string/1) + true -> + raise KeyError, key: ~w[terms data file], term: opts + end + end + + def file!(opts) do + if file = Keyword.get(opts, :file) do + cond do + File.exists?(file) -> + file + File.exists?(expanded_file = Path.expand(file, @vocabs_dir)) -> + expanded_file + true -> + raise File.Error, path: file, action: "find", reason: :enoent + end + end + end + + defp load_file(file) do + RDF.NTriples.Reader.read_file!(file) + end + + defp data_env do + __ENV__ + end + + + defmacro define_vocab_terms(terms, base_uri) do + Enum.map terms, fn term -> +# TODO: Why does this way of precompiling the URI not work? We're getting an "invalid quoted expression: %URI{...}" +# uri = term_to_uri(base_uri, term) +# quote bind_quoted: [uri: Macro.escape(uri), term: String.to_atom(term)] do +## @doc "<#{@tmp_uri}>" +# def unquote(term)() do +# unquote(uri) +# end +# end +# Temporary workaround: + quote do + @tmp_uri term_to_uri(@base_uri, unquote(term)) + @doc "<#{@tmp_uri}>" + def unquote(term |> String.to_atom)(), do: @tmp_uri + end + end + end + + defp data_vocab_terms(data, base_uri) do + data + |> RDF.Graph.resources # TODO: support also RDF.Datasets ... + # filter URIs + |> Stream.filter(fn + %URI{} -> true + _ -> false + end) + |> Stream.map(&to_string/1) + |> Stream.map(&(strip_base_uri(&1, base_uri))) + |> Enum.filter(&vocab_term?/1) + end + + defp group_terms_by_case(terms) do + Enum.group_by terms, fn term -> + if lowercase?(term), + do: :lowercased, + else: :capitalized + end + end + + defp lowercase?(term) when is_atom(term), + do: Atom.to_string(term) |> lowercase? + defp lowercase?(term), + do: term =~ ~r/^\p{Ll}/u + + defp strip_base_uri(uri, base_uri) do + if String.starts_with?(uri, base_uri) do + String.replace_prefix(uri, base_uri, "") + end + end + + defp vocab_term?(term) when is_binary(term) do + not String.contains?(term, "/") + end + defp vocab_term?(_), do: false + + @doc false + def term_to_uri(base_uri, term) do + URI.parse(base_uri <> to_string(term)) + end + +end diff --git a/priv/vocabs/owl.nt b/priv/vocabs/owl.nt new file mode 100644 index 0000000..27f2bc1 --- /dev/null +++ b/priv/vocabs/owl.nt @@ -0,0 +1,450 @@ + . + "DatatypeProperty" . + . + "The class of data properties." . + . + . + "IrreflexiveProperty" . + . + "The class of irreflexive properties." . + . + . + . + "maxQualifiedCardinality" . + . + "The property that determines the cardinality of a maximum qualified cardinality restriction." . + . + . + "Nothing" . + . + "This is the empty class." . + . + . + "AllDifferent" . + . + "The class of collections of pairwise different individuals." . + . + . + "Axiom" . + . + "The class of annotated axioms for which the RDF serialization consists of an annotated subject, predicate and object." . + . + . + "AllDisjointClasses" . + . + "The class of collections of pairwise disjoint classes." . + . + . + "DeprecatedProperty" . + . + "The class of deprecated properties." . + . + . + "OntologyProperty" . + . + "The class of ontology properties." . + . + . + "DeprecatedClass" . + . + "The class of deprecated classes." . + . + . + "AnnotationProperty" . + . + "The class of annotation properties." . + . + . + . + "deprecated" . + . + "The annotation property that indicates that a given entity has been deprecated." . + . + . + . + "maxCardinality" . + . + "The property that determines the cardinality of a maximum cardinality restriction." . + . + . + "AsymmetricProperty" . + . + "The class of asymmetric properties." . + . + . + "DataRange" . + . + "The class of OWL data ranges, which are special kinds of datatypes. Note: The use of the IRI owl:DataRange has been deprecated as of OWL 2. The IRI rdfs:Datatype SHOULD be used instead." . + . + . + . + "priorVersion" . + . + . + "The annotation property that indicates the predecessor ontology of a given ontology." . + . + . + . + "bottomObjectProperty" . + . + "The object property that does not relate any two individuals." . + . + . + "FunctionalProperty" . + . + "The class of functional properties." . + . + . + "NegativePropertyAssertion" . + . + "The class of negative property assertions." . + . + . + . + "propertyChainAxiom" . + . + "The property that determines the n-tuple of properties that build a sub property chain of a given property." . + . + . + "ObjectProperty" . + . + "The class of object properties." . + . + . + . + "bottomDataProperty" . + . + "The data property that does not relate any individual to any data value." . + . + . + "AllDisjointProperties" . + . + "The class of collections of pairwise disjoint properties." . + . + . + "Class" . + . + "The class of OWL classes." . + . + . + . + "onDatatype" . + . + "The property that determines the datatype that a datatype restriction refers to." . + . + . + "Restriction" . + . + "The class of property restrictions." . + . + . + . + . + . + "$Date: 2009/11/15 10:54:12 $" . + "The OWL 2 Schema vocabulary (OWL 2)" . + . + "\r\n This ontology partially describes the built-in classes and\r\n properties that together form the basis of the RDF/XML syntax of OWL 2.\r\n The content of this ontology is based on Tables 6.1 and 6.2\r\n in Section 6.4 of the OWL 2 RDF-Based Semantics specification,\r\n available at http://www.w3.org/TR/owl2-rdf-based-semantics/.\r\n Please note that those tables do not include the different annotations\r\n (labels, comments and rdfs:isDefinedBy links) used in this file.\r\n Also note that the descriptions provided in this ontology do not\r\n provide a complete and correct formal description of either the syntax\r\n or the semantics of the introduced terms (please see the OWL 2\r\n recommendations for the complete and normative specifications).\r\n Furthermore, the information provided by this ontology may be\r\n misleading if not used with care. This ontology SHOULD NOT be imported\r\n into OWL ontologies. Importing this file into an OWL 2 DL ontology\r\n will cause it to become an OWL 2 Full ontology and may have other,\r\n unexpected, consequences.\r\n " . + . + . + . + . + . + "ReflexiveProperty" . + . + "The class of reflexive properties." . + . + . + . + "disjointUnionOf" . + . + "The property that determines that a given class is equivalent to the disjoint union of a collection of other classes." . + . + . + . + "topDataProperty" . + . + "The data property that relates every individual to every data value." . + . + . + . + "minCardinality" . + . + "The property that determines the cardinality of a minimum cardinality restriction." . + . + . + . + "propertyDisjointWith" . + . + "The property that determines that two given properties are disjoint." . + . + . + . + "sameAs" . + . + "The property that determines that two given individuals are equal." . + . + . + . + "onDataRange" . + . + "The property that determines the data range that a qualified data cardinality restriction refers to." . + . + . + . + "intersectionOf" . + . + "The property that determines the collection of classes or data ranges that build an intersection." . + . + . + . + "hasKey" . + . + "The property that determines the collection of properties that jointly build a key." . + . + . + . + "disjointWith" . + . + "The property that determines that two given classes are disjoint." . + . + . + . + "imports" . + . + "The property that is used for importing other ontologies into a given ontology." . + . + . + "NamedIndividual" . + . + "The class of named individuals." . + . + . + . + "topObjectProperty" . + . + "The object property that relates every two individuals." . + . + . + . + "cardinality" . + . + "The property that determines the cardinality of an exact cardinality restriction." . + . + . + . + "onProperty" . + . + "The property that determines the property that a property restriction refers to." . + . + . + . + "withRestrictions" . + . + "The property that determines the collection of facet-value pairs that define a datatype restriction." . + . + . + . + "complementOf" . + . + "The property that determines that a given class is the complement of another class." . + . + . + . + "annotatedTarget" . + . + "The property that determines the object of an annotated axiom or annotated annotation." . + . + . + . + "allValuesFrom" . + . + "The property that determines the class that a universal property restriction refers to." . + . + . + . + "inverseOf" . + . + "The property that determines that two given properties are inverse." . + . + . + . + "members" . + . + "The property that determines the collection of members in either a owl:AllDifferent, owl:AllDisjointClasses or owl:AllDisjointProperties axiom." . + . + . + . + "hasSelf" . + . + "The property that determines the property that a self restriction refers to." . + . + . + . + "targetValue" . + . + "The property that determines the value of a negative data property assertion." . + . + . + . + "minQualifiedCardinality" . + . + "The property that determines the cardinality of a minimum qualified cardinality restriction." . + . + . + . + "equivalentProperty" . + . + "The property that determines that two given properties are equivalent." . + . + . + . + "sourceIndividual" . + . + "The property that determines the subject of a negative property assertion." . + . + . + "TransitiveProperty" . + . + "The class of transitive properties." . + . + . + "Ontology" . + . + "The class of ontologies." . + . + . + "SymmetricProperty" . + . + "The class of symmetric properties." . + . + . + . + "backwardCompatibleWith" . + . + . + "The annotation property that indicates that a given ontology is backward compatible with another ontology." . + . + . + . + "unionOf" . + . + "The property that determines the collection of classes or data ranges that build a union." . + . + . + . + "hasValue" . + . + "The property that determines the individual that a has-value restriction refers to." . + . + . + . + "onProperties" . + . + "The property that determines the n-tuple of properties that a property restriction on an n-ary data range refers to." . + . + . + . + "differentFrom" . + . + "The property that determines that two given individuals are different." . + . + . + . + "someValuesFrom" . + . + "The property that determines the class that an existential property restriction refers to." . + . + . + . + "distinctMembers" . + . + "The property that determines the collection of pairwise different individuals in a owl:AllDifferent axiom." . + . + . + "InverseFunctionalProperty" . + . + "The class of inverse-functional properties." . + . + . + . + "oneOf" . + . + "The property that determines the collection of individuals or data values that build an enumeration." . + . + . + . + "annotatedSource" . + . + "The property that determines the subject of an annotated axiom or annotated annotation." . + . + . + . + "versionInfo" . + . + "The annotation property that provides version information for an ontology or another OWL construct." . + . + . + . + "qualifiedCardinality" . + . + "The property that determines the cardinality of an exact qualified cardinality restriction." . + . + . + . + "datatypeComplementOf" . + . + "The property that determines that a given data range is the complement of another data range with respect to the data domain." . + . + . + . + "onClass" . + . + "The property that determines the class that a qualified object cardinality restriction refers to." . + . + . + . + "targetIndividual" . + . + "The property that determines the object of a negative object property assertion." . + . + . + . + "annotatedProperty" . + . + "The property that determines the predicate of an annotated axiom or annotated annotation." . + . + . + . + "assertionProperty" . + . + "The property that determines the predicate of a negative property assertion." . + . + . + "Thing" . + . + "The class of OWL individuals." . + . + . + "incompatibleWith" . + . + . + "The annotation property that indicates that a given ontology is incompatible with another ontology." . + . + . + . + "versionIRI" . + . + "The property that identifies the version IRI of an ontology." . + . + . + "Annotation" . + . + "The class of annotated annotations for which the RDF serialization consists of an annotated subject, predicate and object." . + . + . + . + "equivalentClass" . + . + "The property that determines that two given classes are equivalent, and that is used to specify datatype definitions." . + . diff --git a/priv/vocabs/rdf.nt b/priv/vocabs/rdf.nt new file mode 100644 index 0000000..8267e03 --- /dev/null +++ b/priv/vocabs/rdf.nt @@ -0,0 +1,102 @@ + "This is the RDF Schema for the RDF vocabulary terms in the RDF Namespace, defined in RDF 1.1 Concepts." . + . + "The RDF Concepts Vocabulary (RDF)" . + . + "rest" . + . + . + . + "The rest of the subject RDF list after the first item." . + . + "Statement" . + . + "The class of RDF statements." . + . + . + "Alt" . + . + "The class of containers of alternatives." . + . + . + "first" . + . + . + . + "The first item in the subject RDF list." . + . + "subject" . + . + . + . + "The subject of the subject RDF statement." . + . + "Seq" . + . + "The class of ordered containers." . + . + . + "Property" . + . + "The class of RDF properties." . + . + . + "nil" . + . + "The empty list, with no items in it. If the rest of a list is nil then the list has no more items in it." . + . + "List" . + . + "The class of RDF Lists." . + . + . + "type" . + . + . + . + "The subject is an instance of a class." . + . + "value" . + . + . + . + "Idiomatic property used for structured values." . + . + . + . + . + "The class of plain (i.e. untyped) literal values, as used in RIF and OWL 2" . + "PlainLiteral" . + . + . + . + . + "The datatype of RDF literals storing fragments of HTML content" . + "HTML" . + . + . + . + . + "The datatype of language-tagged string values" . + "langString" . + . + "predicate" . + . + . + . + "The predicate of the subject RDF statement." . + . + . + . + "The datatype of XML literal values." . + "XMLLiteral" . + . + "object" . + . + . + . + "The object of the subject RDF statement." . + . + "Bag" . + . + "The class of unordered containers." . + . diff --git a/priv/vocabs/rdfs.nt b/priv/vocabs/rdfs.nt new file mode 100644 index 0000000..07d30d5 --- /dev/null +++ b/priv/vocabs/rdfs.nt @@ -0,0 +1,87 @@ + "seeAlso" . + . + . + . + . + "Further information about the subject resource." . + . + "ContainerMembershipProperty" . + . + . + "The class of container membership properties, rdf:_1, rdf:_2, ...,\n all of which are sub-properties of 'member'." . + . + "Literal" . + . + . + "The class of literal values, eg. textual strings and integers." . + "subClassOf" . + . + . + . + . + "The subject is a subclass of a class." . + "subPropertyOf" . + . + . + . + . + "The subject is a subproperty of a property." . + "label" . + . + . + . + . + "A human-readable name for the subject." . + "range" . + . + . + . + . + "A range of the subject property." . + . + "isDefinedBy" . + . + . + . + . + "The defininition of the subject resource." . + "domain" . + . + . + . + . + "A domain of the subject property." . + . + . + "The RDF Schema vocabulary (RDFS)" . + "Resource" . + . + . + "The class resource, everything." . + . + "Datatype" . + . + . + "The class of RDF datatypes." . + . + "Container" . + . + . + "The class of RDF containers." . + "member" . + . + . + . + . + "A member of the subject resource." . + "comment" . + . + . + . + . + "A description of the subject resource." . + . + "Class" . + . + . + "The class of classes." . diff --git a/priv/vocabs/skos.nt b/priv/vocabs/skos.nt new file mode 100644 index 0000000..3e96b9b --- /dev/null +++ b/priv/vocabs/skos.nt @@ -0,0 +1,252 @@ + . + . + . + "scope note"@en . + "A note that helps to clarify the meaning and/or the use of a concept."@en . + . + . + . + . + "is in scheme"@en . + . + "Relates a resource (for example a concept) to a concept scheme in which it is included."@en . + "A concept may be a member of more than one concept scheme."@en . + . + . + "Ordered Collection"@en . + . + "An ordered collection of concepts, where both the grouping and the ordering are meaningful."@en . + "Ordered collections can be used where you would like a set of concepts to be displayed in a specific order, and optionally under a 'node label'."@en . + . + . + . + "notation"@en . + "A notation, also known as classification code, is a string of characters such as \"T58.5\" or \"303.4833\" used to uniquely identify a concept within the scope of a given concept scheme."@en . + "By convention, skos:notation is used with a typed literal in the object position of the triple."@en . + . + . + . + "skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties."@en . + "The range of skos:hiddenLabel is the class of RDF plain literals."@en . + "hidden label"@en . + "A lexical label for a resource that should be hidden when generating visual displays of the resource, but should still be accessible to free text search operations."@en . + . + . + . + . + "Acronyms, abbreviations, spelling variants, and irregular plural/singular forms may be included among the alternative labels for a concept. Mis-spelled terms are normally included as hidden labels (see skos:hiddenLabel)."@en . + "skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise disjoint properties."@en . + "The range of skos:altLabel is the class of RDF plain literals."@en . + "alternative label"@en . + "An alternative lexical label for a resource."@en . + . +_:g70202337373100 . +_:g70202337373100 _:g70202337354660 . + . + . + . + . + "has narrower match"@en . + "skos:narrowMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes."@en . + . + . + . + . + . + "These concept mapping relations mirror semantic relations, and the data model defined below is similar (with the exception of skos:exactMatch) to the data model defined for semantic relations. A distinct vocabulary is provided for concept mapping relations, to provide a convenient way to differentiate links within a concept scheme from links between concept schemes. However, this pattern of usage is not a formal requirement of the SKOS data model, and relies on informal definitions of best practice."@en . + "is in mapping relation with"@en . + "Relates two concepts coming, by convention, from different schemes, and that have comparable meanings"@en . + . + . + . + . + . + . + "has broader transitive"@en . + "skos:broaderTransitive is a transitive superproperty of skos:broader." . + "By convention, skos:broaderTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application."@en . + . + . + . + . + . + . + "has top concept"@en . + . + "Relates, by convention, a concept scheme to a concept which is topmost in the broader/narrower concept hierarchies for that scheme, providing an entry point to these hierarchies."@en . + . + . + . + . + "has member"@en . + _:g70202337373100 . + "Relates a collection to one of its members."@en . + . + . + . + . + "skos:related is disjoint with skos:broaderTransitive"@en . + "has related"@en . + "Relates a concept to a concept with which there is an associative semantic relationship."@en . + . + . + . + . + . + . + "has member list"@en . + . + "Relates an ordered collection to the RDF list containing its members."@en . + "For any resource, every item in the list given as the value of the\n skos:memberList property is also a value of the skos:member property."@en . + . + . + "Thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', and other types of controlled vocabulary are all examples of concept schemes. Concept schemes are also embedded in glossaries and terminologies."@en . + "Concept Scheme"@en . + . + "A set of concepts, optionally including statements about semantic relationships between those concepts."@en . + "A concept scheme may be defined to include concepts from different sources."@en . + . + . + . + . + "is in semantic relation with"@en . + . + "Links a concept to a concept related by meaning."@en . + "This property should not be used directly, but as a super-property for all properties denoting a relationship of meaning between concepts."@en . + "An RDF vocabulary for describing the basic structure and content of concept schemes such as thesauri, classification schemes, subject heading lists, taxonomies, 'folksonomies', other types of controlled vocabulary, and also concept schemes embedded in glossaries and terminologies."@en . + "Sean Bechhofer" . + "Alistair Miles" . + . + "SKOS Vocabulary"@en . + "Participants in W3C's Semantic Web Deployment Working Group." . + "Dave Beckett" . + "Nikki Rogers" . + . + . + . + "Concept"@en . + "An idea or notion; a unit of thought."@en . + . + . + "Collection"@en . + . + . + "A meaningful collection of concepts."@en . + "Labelled collections can be used where you would like a set of concepts to be displayed under a 'node label' in the hierarchy."@en . + . + . + . + . + "Narrower concepts are typically rendered as children in a concept hierarchy (tree)."@en . + "has narrower"@en . + "Relates a concept to a concept that is more specific in meaning."@en . + "By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources."@en . + . + . + . + . + "definition"@en . + "A statement or formal explanation of the meaning of a concept."@en . + . + . + . + . + "history note"@en . + "A note about the past state/use/meaning of a concept."@en . + . + . + . + . + . + "has related match"@en . + "skos:relatedMatch is used to state an associative mapping link between two conceptual resources in different concept schemes."@en . + . + . + . + . + . + . + . + "has narrower transitive"@en . + "skos:narrowerTransitive is a transitive superproperty of skos:narrower." . + "By convention, skos:narrowerTransitive is not used to make assertions. Rather, the properties can be used to draw inferences about the transitive closure of the hierarchical relation, which is useful e.g. when implementing a simple query expansion algorithm in a search application."@en . + . + . + . + . + "The range of skos:prefLabel is the class of RDF plain literals."@en . + "skos:prefLabel, skos:altLabel and skos:hiddenLabel are pairwise\n disjoint properties."@en . + "A resource has no more than one value of skos:prefLabel per language tag, and no more than one value of skos:prefLabel without language tag."@en . + "preferred label"@en . + "The preferred lexical label for a resource, in a given language."@en . + . +_:g70202337354580 . +_:g70202337354580 . + . + . + . + . + "Broader concepts are typically rendered as parents in a concept hierarchy (tree)."@en . + "has broader"@en . + "Relates a concept to a concept that is more general in meaning."@en . + "By convention, skos:broader is only used to assert an immediate (i.e. direct) hierarchical link between two conceptual resources."@en . + . + . + . + . + . + "has broader match"@en . + "skos:broadMatch is used to state a hierarchical mapping link between two conceptual resources in different concept schemes."@en . + . + . + . + . + . + "example"@en . + "An example of the use of a concept."@en . + . + . + . + . + "change note"@en . + "A note about a modification to a concept."@en . + . + . + . + . + . + "has close match"@en . + "skos:closeMatch is used to link two concepts that are sufficiently similar that they can be used interchangeably in some information retrieval applications. In order to avoid the possibility of \"compound errors\" when combining mappings across more than two concept schemes, skos:closeMatch is not declared to be a transitive property."@en . + . + . + . + . + . + . + "skos:exactMatch is disjoint with each of the properties skos:broadMatch and skos:relatedMatch."@en . + "has exact match"@en . + "skos:exactMatch is used to link two concepts, indicating a high degree of confidence that the concepts can be used interchangeably across a wide range of information retrieval applications. skos:exactMatch is a transitive property, and is a sub-property of skos:closeMatch."@en . + . + . + . + . + . + . + "is top concept in scheme"@en . + . + "Relates a concept to the concept scheme that it is a top level concept of."@en . + . + . + . + . + "note"@en . + "A general note, for any purpose."@en . + "This property may be used directly, or as a super-property for more specific note types."@en . + . + . + . + "editorial note"@en . + "A note for an editor, translator or maintainer of the vocabulary."@en . + . +_:g70202337354660 . +_:g70202337354660 _:g70202337354580 . diff --git a/test/data/vocab_ns_example2.nt b/test/data/vocab_ns_example2.nt new file mode 100644 index 0000000..a9b632f --- /dev/null +++ b/test/data/vocab_ns_example2.nt @@ -0,0 +1,2 @@ + . + . diff --git a/test/support/rdf_case.ex b/test/support/rdf_case.ex index 05287f9..b6d32fd 100644 --- a/test/support/rdf_case.ex +++ b/test/support/rdf_case.ex @@ -1,16 +1,18 @@ defmodule RDF.Test.Case do use ExUnit.CaseTemplate + use RDF.Vocabulary.Namespace + defvocab EX, + base_uri: "http://example.com/", + terms: [], strict: false + alias RDF.{Dataset, Graph, Description} import RDF, only: [uri: 1] - defmodule EX, do: - use RDF.Vocabulary, base_uri: "http://example.com/" - using do quote do alias RDF.{Dataset, Graph, Description} - alias EX + alias RDF.Test.Case.EX import RDF, only: [uri: 1, literal: 1, bnode: 1] import RDF.Test.Case diff --git a/test/unit/literal_test.exs b/test/unit/literal_test.exs index 5592650..f75b9e9 100644 --- a/test/unit/literal_test.exs +++ b/test/unit/literal_test.exs @@ -1,9 +1,11 @@ defmodule RDF.LiteralTest do use ExUnit.Case + alias RDF.{Literal} + alias RDF.NS.XSD + doctest RDF.Literal - alias RDF.{Literal, XSD} describe "construction by type inference" do test "creating an string literal" do diff --git a/test/unit/namespace_test.exs b/test/unit/namespace_test.exs new file mode 100644 index 0000000..84cea9a --- /dev/null +++ b/test/unit/namespace_test.exs @@ -0,0 +1,7 @@ +defmodule RDF.NamespaceTest do + use ExUnit.Case + + alias RDF.NS.RDFS + doctest RDF.Namespace + +end diff --git a/test/unit/ntriples/reader_test.exs b/test/unit/ntriples/reader_test.exs index 9aaf4cf..d9ab479 100644 --- a/test/unit/ntriples/reader_test.exs +++ b/test/unit/ntriples/reader_test.exs @@ -5,8 +5,17 @@ defmodule RDF.NTriples.ReaderTest do alias RDF.{Graph, TestData} - defmodule EX, do: use RDF.Vocabulary, base_uri: "http://example.org/#" - defmodule P, do: use RDF.Vocabulary, base_uri: "http://www.perceive.net/schemas/relationship/" + + use RDF.Vocabulary.Namespace + + defvocab EX, + base_uri: "http://example.org/#", + terms: [], strict: false + + defvocab P, + base_uri: "http://www.perceive.net/schemas/relationship/", + terms: [], strict: false + @w3c_ntriples_test_suite Path.join(TestData.dir, "N-TRIPLES-TESTS") diff --git a/test/unit/rdf_test.exs b/test/unit/rdf_test.exs index a8edf0e..a7c87e6 100644 --- a/test/unit/rdf_test.exs +++ b/test/unit/rdf_test.exs @@ -1,7 +1,8 @@ defmodule RDF.CoreTest do use ExUnit.Case - defmodule EX, do: use RDF.Vocabulary, base_uri: "http://example.com/" + use RDF.Vocabulary.Namespace + defvocab EX, base_uri: "http://example.com/", terms: [], strict: false doctest RDF diff --git a/test/unit/vocabulary_namespace_test.exs b/test/unit/vocabulary_namespace_test.exs new file mode 100644 index 0000000..2ca17b0 --- /dev/null +++ b/test/unit/vocabulary_namespace_test.exs @@ -0,0 +1,182 @@ +defmodule RDF.Vocabulary.NamespaceTest do + use ExUnit.Case + + doctest RDF.Vocabulary.Namespace + + defmodule TestNS do + use RDF.Vocabulary.Namespace + + defvocab Example1, + base_uri: "http://example.com/example1#", + data: RDF.Graph.new([ + {"http://example.com/example1#foo", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "http://www.w3.org/1999/02/22-rdf-syntax-ns#Property"}, + {"http://example.com/example1#Bar", "http://www.w3.org/1999/02/22-rdf-syntax-ns#type", "http://www.w3.org/2000/01/rdf-schema#Resource"} + ]) + + defvocab Example2, + base_uri: "http://example.com/example2/", + file: "test/data/vocab_ns_example2.nt" +# file: "vocab_ns_example2.nt" + + defvocab Example3, + base_uri: "http://example.com/example3#", + terms: ~w[foo Bar] + + defvocab Example4, + base_uri: "http://example.com/example4#", + terms: ~w[foo Bar], + strict: false + end + + +# test "__all__ returns a list of all defined namespaces" do +# assert Enum.count(TestNS.__all__) == 4 +# assert is_map(TestNS.__all__) +# assert Map.has_key?(TestNS.__all__, :Example1) +# assert Map.has_key?(TestNS.__all__, :Example2) +# assert Map.has_key?(TestNS.__all__, :Example3) +# assert Map.has_key?(TestNS.__all__, :Example4) +# end + + describe "defvocab" do + test "fails without a base_uri" do + assert_raise KeyError, fn -> + defmodule BadNS1 do + use RDF.Vocabulary.Namespace + + defvocab Example, terms: [] + end + end + end + + test "fails, when the base_uri doesn't end with '/' or '#'" do + assert_raise RDF.Namespace.InvalidVocabBaseURIError, fn -> + defmodule BadNS2 do + use RDF.Vocabulary.Namespace + + defvocab Example, + base_uri: "http://example.com/base_uri4", + terms: [] + end + end + end + + @tag skip: "TODO: implement proper URI validation" + test "fails, when the base_uri isn't a valid URI according to RFC 3986" do + assert_raise RDF.Namespace.InvalidVocabBaseURIError, fn -> + defmodule BadNS3 do + use RDF.Vocabulary.Namespace + + defvocab Example, + base_uri: "foo/", + terms: [] + end + end + assert_raise RDF.Namespace.InvalidVocabBaseURIError, fn -> + defmodule BadNS4 do + use RDF.Vocabulary.Namespace + + defvocab Example, + base_uri: :foo, + terms: [] + end + end + end + + test "fails, when the given file not found" do + assert_raise File.Error, fn -> + defmodule BadNS5 do + use RDF.Vocabulary.Namespace + + defvocab Example, + base_uri: "http://example.com/ex5#", + file: "something.nt" + end + end + end + + end + + test "__base_uri__ returns the base_uri" do + alias TestNS.Example1, as: HashVocab + alias TestNS.Example2, as: SlashVocab + + assert HashVocab.__base_uri__ == "http://example.com/example1#" + assert SlashVocab.__base_uri__ == "http://example.com/example2/" + end + + test "__terms__ returns a list of all defined terms" do + alias TestNS.Example1 + assert length(Example1.__terms__) == 2 + assert :foo in Example1.__terms__ + assert :Bar in Example1.__terms__ + end + + @tag skip: "TODO: Can we make RDF.uri(:foo) an undefined function call with guards or in another way?" + test "resolving an unqualified term raises an error" do + assert_raise UndefinedFunctionError, fn -> RDF.uri(:foo) end + end + + describe "term resolution in a strict vocab namespace" do + alias TestNS.{Example1, Example2, Example3} + test "undefined terms" do + assert_raise UndefinedFunctionError, fn -> + Example1.undefined + end + assert_raise UndefinedFunctionError, fn -> + Example2.undefined + end + assert_raise UndefinedFunctionError, fn -> + Example3.undefined + end + + assert_raise RDF.Namespace.UndefinedTermError, fn -> + RDF.Namespace.resolve_term(TestNS.Example1.Undefined) + end + assert_raise RDF.Namespace.UndefinedTermError, fn -> + RDF.Namespace.resolve_term(Example2.Undefined) + end + assert_raise RDF.Namespace.UndefinedTermError, fn -> + RDF.Namespace.resolve_term(Example3.Undefined) + end + end + + test "lowercased terms" do + assert Example1.foo == URI.parse("http://example.com/example1#foo") + assert RDF.uri(Example1.foo) == URI.parse("http://example.com/example1#foo") + + assert Example2.foo == URI.parse("http://example.com/example2/foo") + assert RDF.uri(Example2.foo) == URI.parse("http://example.com/example2/foo") + + assert Example3.foo == URI.parse("http://example.com/example3#foo") + assert RDF.uri(Example3.foo) == URI.parse("http://example.com/example3#foo") + end + + test "captitalized terms" do + assert RDF.uri(Example1.Bar) == URI.parse("http://example.com/example1#Bar") + assert RDF.uri(Example2.Bar) == URI.parse("http://example.com/example2/Bar") + assert RDF.uri(Example3.Bar) == URI.parse("http://example.com/example3#Bar") + end + + end + + describe "term resolution in a non-strict vocab namespace" do + alias TestNS.Example4 + test "undefined lowercased terms" do + assert Example4.random == URI.parse("http://example.com/example4#random") + end + + test "undefined capitalized terms" do + assert RDF.uri(Example4.Random) == URI.parse("http://example.com/example4#Random") + end + + test "defined lowercase terms" do + assert Example4.foo == URI.parse("http://example.com/example4#foo") + end + + test "defined capitalized terms" do + assert RDF.uri(Example4.Bar) == URI.parse("http://example.com/example4#Bar") + end + end + +end diff --git a/test/unit/vocabulary_test.exs b/test/unit/vocabulary_test.exs deleted file mode 100644 index 52ab518..0000000 --- a/test/unit/vocabulary_test.exs +++ /dev/null @@ -1,127 +0,0 @@ -defmodule RDF.VocabularyTest do - use ExUnit.Case - - doctest RDF.Vocabulary - - - defmodule StrictVocab, do: - use RDF.Vocabulary, base_uri: "http://example.com/strict_vocab/", strict: true - - defmodule NonStrictVocab, do: - use RDF.Vocabulary, base_uri: "http://example.com/non_strict_vocab/" - - defmodule HashVocab, do: - use RDF.Vocabulary, base_uri: "http://example.com/hash_vocab#" - - defmodule SlashVocab, do: - use RDF.Vocabulary, base_uri: "http://example.com/slash_vocab/" - - - describe "base_uri" do - test "__base_uri__ returns the base_uri" do - assert SlashVocab.__base_uri__ == "http://example.com/slash_vocab/" - assert HashVocab.__base_uri__ == "http://example.com/hash_vocab#" - end - - test "a Vocabulary can't be defined without a base_uri" do - assert_raise RDF.Vocabulary.InvalidBaseURIError, fn -> - defmodule TestBaseURIVocab3, do: use RDF.Vocabulary - end - end - - test "it is not valid, when it doesn't end with '/' or '#'" do - assert_raise RDF.Vocabulary.InvalidBaseURIError, fn -> - defmodule TestBaseURIVocab4, do: - use RDF.Vocabulary, base_uri: "http://example.com/base_uri4" - end - end - - @tag skip: "TODO: implement proper URI validation" - test "it is not valid, when it isn't a valid URI according to RFC 3986" do - assert_raise RDF.Vocabulary.InvalidBaseURIError, fn -> - defmodule TestBaseURIVocab5, do: use RDF.Vocabulary, base_uri: "foo/" - end - assert_raise RDF.Vocabulary.InvalidBaseURIError, fn -> - defmodule TestBaseURIVocab6, do: use RDF.Vocabulary, base_uri: :foo - end - end - end - - test "__terms__ returns a list of all defined terms" do - defmodule VocabWithSomeTerms do - use RDF.Vocabulary, base_uri: "http://example.com/test5/" - defuri :prop - defuri :Foo - end - - assert length(VocabWithSomeTerms.__terms__) == 2 - assert :prop in VocabWithSomeTerms.__terms__ - assert :Foo in VocabWithSomeTerms.__terms__ - end - - @tag skip: "TODO: Can we make RDF.uri(:foo) an undefined function call with guards or in another way?" - test "resolving an unqualified term raises an error" do - assert_raise UndefinedFunctionError, fn -> RDF.uri(:foo) end - # or: assert_raise InvalidTermError, fn -> RDF.uri(:foo) end - end - - test "resolving undefined terms of a non-strict vocabulary" do - assert NonStrictVocab.foo == - URI.parse("http://example.com/non_strict_vocab/foo") - assert RDF.uri(NonStrictVocab.Bar) == - URI.parse("http://example.com/non_strict_vocab/Bar") - end - - test "resolving undefined terms of a strict vocabulary" do - assert_raise UndefinedFunctionError, fn -> StrictVocab.foo end - assert_raise RDF.Vocabulary.UndefinedTermError, fn -> - RDF.uri(StrictVocab.Foo) end - end - - test "resolving manually defined lowercase terms on a non-strict vocabulary" do - defmodule TestManualVocab1 do - use RDF.Vocabulary, base_uri: "http://example.com/manual_vocab1/" - defuri :prop - end - - assert TestManualVocab1.prop == - URI.parse("http://example.com/manual_vocab1/prop") - assert RDF.uri(TestManualVocab1.prop) == - URI.parse("http://example.com/manual_vocab1/prop") - end - - test "resolving manually defined uppercase terms on a non-strict vocabulary" do - defmodule TestManualVocab2 do - use RDF.Vocabulary, base_uri: "http://example.com/manual_vocab2/" - defuri :Foo - end - - assert RDF.uri(TestManualVocab2.Foo) == - URI.parse("http://example.com/manual_vocab2/Foo") - end - - test "resolving manually defined lowercase terms on a strict vocabulary" do - defmodule TestManualStrictVocab1 do - use RDF.Vocabulary, - base_uri: "http://example.com/manual_strict_vocab1/", strict: true - defuri :prop - end - - assert TestManualStrictVocab1.prop == - URI.parse("http://example.com/manual_strict_vocab1/prop") - assert RDF.uri(TestManualStrictVocab1.prop) == - URI.parse("http://example.com/manual_strict_vocab1/prop") - end - - test "resolving manually defined uppercase terms on a strict vocabulary" do - defmodule TestManualStrictVocab2 do - use RDF.Vocabulary, - base_uri: "http://example.com/manual_strict_vocab2/", strict: true - defuri :Foo - end - - assert RDF.uri(TestManualStrictVocab2.Foo) == - URI.parse("http://example.com/manual_strict_vocab2/Foo") - end - -end