From 44782b38133acd7b4d8f34247fd08463b3f33281 Mon Sep 17 00:00:00 2001 From: Rob van den Bogaard Date: Thu, 19 Apr 2018 17:53:28 +0200 Subject: [PATCH 1/2] [rdf] Support for translated literal object values --- modules/mod_ginger_rdf/models/m_rdf.erl | 71 ++++++++++++-- modules/mod_ginger_rdf/tests/m_rdf_tests.erl | 98 ++++++++++++++++++++ 2 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 modules/mod_ginger_rdf/tests/m_rdf_tests.erl diff --git a/modules/mod_ginger_rdf/models/m_rdf.erl b/modules/mod_ginger_rdf/models/m_rdf.erl index f9055370c..f2d9e1371 100644 --- a/modules/mod_ginger_rdf/models/m_rdf.erl +++ b/modules/mod_ginger_rdf/models/m_rdf.erl @@ -57,10 +57,10 @@ m_find_value(id, #m{value = #rdf_resource{id = Id}}, _Context) -> Id; m_find_value(uri, #m{value = #rdf_resource{id = Id}}, _Context) -> Id; -m_find_value(Predicate, #m{value = #rdf_resource{triples = Triples}}, _Context) -> +m_find_value(Predicate, #m{value = #rdf_resource{triples = Triples}}, Context) -> case lookup_triple(Predicate, Triples) of undefined -> undefined; - #triple{object = Object} -> literal_object_value(Object) + #triple{object = Object} -> literal_object_value(Object, Context) end. m_to_list(_, _Context) -> @@ -622,10 +622,67 @@ to_json_ld(Id, Context) -> jsx:encode(ginger_json_ld:serialize_to_map(to_triples(Id, Context))). %% @doc Extract literal value from triple object(s). --spec literal_object_value([#rdf_value{}] | #rdf_value{} | any()) -> binary() | list(). -literal_object_value(Objects) when is_list(Objects) -> - [literal_object_value(Object) || Object <- Objects]; -literal_object_value(#rdf_value{value = Value}) -> +-spec literal_object_value([#rdf_value{}] | #rdf_value{} | any(), #context{}) -> binary() | list(). +literal_object_value(Objects, Context) when is_list(Objects) -> + literal_object_value_translation(Objects, Context); +literal_object_value(#rdf_value{value = Value}, _Context) -> Value; -literal_object_value(Other) -> +literal_object_value(Other, _Context) -> Other. + +%% @doc Determine whether a given language matches the expected (or desired) language. +%% E.g. given language <<"en-GB">> should match :en as well as <<"en">>. +-spec language_matches(binary(), atom() | binary()) -> boolean(). +language_matches(LanguageGiven, LanguageExpected) when is_atom(LanguageExpected) -> + language_matches(LanguageGiven, atom_to_binary(LanguageExpected, utf8)); +language_matches(LanguageGiven, LanguageExpected) when is_binary(LanguageGiven), is_binary(LanguageExpected) -> + case binary:match(LanguageGiven, LanguageExpected) of + {0, _} -> + true; + _ -> + false + end; +language_matches(_, _) -> + false. + +%% @doc Select matching translations and untranslated values from a list of #rdf_value. +-spec literal_object_value_translation([#rdf_value{}] | any(), #context{}) -> binary() | list(). +literal_object_value_translation([#rdf_value{}|_] = Objects, #context{} = Context) -> + Language = z_context:language(Context), + % First try to match the preferred language + % nl as set language should match values like nl-NL + { Translations, OtherValues } = + lists:foldr( + fun( Object, { Ts, OVs } ) -> + case Object of + #rdf_value{value = V, language = undefined} -> + { Ts, [V|OVs] }; + #rdf_value{value = _, language = _} -> + { [Object|Ts], OVs }; + _ -> + { Ts, [Object|OVs] } + end + end, + { [], [] }, + Objects + ), + Translated = + case [V || #rdf_value{value = V, language = L} <- Translations, language_matches(L, Language)] of + [] -> + % Fall back to default language + % Should we also see if we have a z_context:fallback_language()? + DefaultLanguage = z_trans:default_language(Context), + [V || #rdf_value{value = V, language = L} <- Translations, language_matches(L, DefaultLanguage)]; + Vs -> Vs + end, + % Include any untranslated values + case Translated ++ OtherValues of + [] -> + undefined; + [SingleValue] -> + SingleValue; + Values -> + Values + end; +literal_object_value_translation(Objects, _Context) -> + Objects. diff --git a/modules/mod_ginger_rdf/tests/m_rdf_tests.erl b/modules/mod_ginger_rdf/tests/m_rdf_tests.erl new file mode 100644 index 000000000..f962836be --- /dev/null +++ b/modules/mod_ginger_rdf/tests/m_rdf_tests.erl @@ -0,0 +1,98 @@ +-module(m_rdf_tests). + +-include_lib("eunit/include/eunit.hrl"). +-include("zotonic.hrl"). +-include("../include/rdf.hrl"). + +m_find_value_test() -> + Input = #rdf_resource{ + id = <<"http://dinges.com/123">>, + triples = [ + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"dc:author">>, + object = [ + #rdf_value{ + value = <<"Author 1">> + }, + #rdf_value{ + value = <<"Author 2">> + } + ] + }, + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"single_value">>, + object = #rdf_value{ + value = <<"Value1">> + } + }, + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"single_value_in_list">>, + object = [#rdf_value{ + value = <<"Value2">> + }] + }, + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"nonrecord_content_in_list">>, + object = [<<"Value3">>] + }, + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"empty_list_object">>, + object = [] + }, + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"nonrecord_content">>, + object = <<"Value">> + }, + #triple{ + subject = <<"http://dinges.com/123">>, + predicate = <<"dc:title">>, + object = [ + #rdf_value{ + language = <<"nl-NL">>, + value = <<"Goed verhaal broer!">> + }, + #rdf_value{ + language = <<"en-US">>, + value = <<"Good story bro!">> + } + ] + } + ] + }, + Context = z_context:new(testsandboxdb), + ContextWithNonPresentLanguage = z_context:set_language(de, Context), + ?assertEqual( + <<"Good story bro!">>, + m_rdf:m_find_value(<<"dc:title">>, #m{value = Input}, ContextWithNonPresentLanguage) + ), + ContextWithLanguage = z_context:set_language(nl, Context), + ?assertEqual( + [<<"Author 1">>, <<"Author 2">>], + m_rdf:m_find_value(<<"dc:author">>, #m{value = Input}, ContextWithLanguage) + ), + ?assertEqual( + <<"Value">>, + m_rdf:m_find_value(<<"nonrecord_content">>, #m{value = Input}, ContextWithLanguage) + ), + ?assertEqual( + <<"Value1">>, + m_rdf:m_find_value(<<"single_value">>, #m{value = Input}, ContextWithLanguage) + ), + ?assertEqual( + <<"Value2">>, + m_rdf:m_find_value(<<"single_value_in_list">>, #m{value = Input}, ContextWithLanguage) + ), + ?assertEqual( + [<<"Value3">>], + m_rdf:m_find_value(<<"nonrecord_content_in_list">>, #m{value = Input}, ContextWithLanguage) + ), + ?assertEqual( + <<"Goed verhaal broer!">>, + m_rdf:m_find_value(<<"dc:title">>, #m{value = Input}, ContextWithLanguage) + ). From d494e6da9bd787f4957375a1721f7afdaf213fd2 Mon Sep 17 00:00:00 2001 From: David de Boer Date: Tue, 26 Jun 2018 21:24:07 +0200 Subject: [PATCH 2/2] Run m_rdf_tests on CI --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3ac16cd9b..8cf3eef30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ script: - docker version - docker-compose version - docker-compose run --rm zotonic make - - docker-compose run zotonic bin/zotonic runtests mod_ginger_activity mod_ginger_collection mod_ginger_rdf + - docker-compose run zotonic bin/zotonic runtests mod_ginger_activity mod_ginger_collection mod_ginger_rdf m_rdf notifications: slack: