Packages

XQuery 3.1 Processor and XMl Database

Current section

Files

Jump to
xqerl src xqerl_mod_map.erl
Raw

src/xqerl_mod_map.erl

%% -------------------------------------------------------------------
%%
%% xqerl - XQuery processor
%%
%% Copyright (c) 2017-2020 Zachary N. Dean All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% -------------------------------------------------------------------
%% @doc Implementation of the "http://www.w3.org/2005/xpath-functions/map"
%% namespace.
-module(xqerl_mod_map).
-include("xqerl.hrl").
-compile(inline_list_funcs).
-define(VAL(V), xqerl_seq3:singleton_value(V)).
-define(NS, <<"http://www.w3.org/2005/xpath-functions/map">>).
-define(PX, <<"map">>).
% block array:array(_) warnings
-dialyzer(no_opaque).
-define(IS_ARRAY(A), is_tuple(A), element(1, A) =:= array).
-export(['contains'/3]).
-export(['entry'/3]).
-export(['find'/3]).
-export(['for-each'/3]).
-export(['get'/3]).
-export(['keys'/2]).
-export(['merge'/2, 'merge'/3]).
-export(['put'/4]).
-export(['remove'/3]).
-export(['size'/2]).
% internal use
-export([equal/2]).
-export([values/1]).
-export([get_matched/2]).
-export([construct/2]).
-functions([
{{qname, ?NS, ?PX, <<"contains">>}, {seqType, 'xs:boolean', one}, [], {'contains', 3}, 2, [
{seqType, {funTest, map, [], undefined, any, any}, one},
{seqType, 'xs:anyAtomicType', one}
]},
{
{qname, ?NS, ?PX, <<"entry">>},
{seqType, {funTest, map, [], undefined, any, any}, one},
[],
{'entry', 3},
2,
[{seqType, 'xs:anyAtomicType', one}, {seqType, item, zero_or_many}]
},
{
{qname, ?NS, ?PX, <<"find">>},
{seqType, {funTest, array, [], undefined, any, any}, one},
[],
{'find', 3},
2,
[{seqType, item, zero_or_many}, {seqType, 'xs:anyAtomicType', one}]
},
{{qname, ?NS, ?PX, <<"for-each">>}, {seqType, item, zero_or_many}, [], {'for-each', 3}, 2, [
{seqType, {funTest, map, [], undefined, any, any}, one},
{seqType,
{funTest, function, [], undefined,
[{seqType, 'xs:anyAtomicType', one}, {seqType, item, zero_or_many}],
{seqType, item, zero_or_many}},
one}
]},
{{qname, ?NS, ?PX, <<"get">>}, {seqType, item, zero_or_many}, [], {'get', 3}, 2, [
{seqType, {funTest, map, [], undefined, any, any}, one},
{seqType, 'xs:anyAtomicType', one}
]},
{
{qname, ?NS, ?PX, <<"keys">>},
{seqType, 'xs:anyAtomicType', zero_or_many},
[],
{'keys', 2},
1,
[{seqType, {funTest, map, [], undefined, any, any}, one}]
},
{
{qname, ?NS, ?PX, <<"merge">>},
{seqType, {funTest, map, [], undefined, any, any}, one},
[],
{'merge', 2},
1,
[{seqType, {funTest, map, [], undefined, any, any}, zero_or_many}]
},
{
{qname, ?NS, ?PX, <<"merge">>},
{seqType, {funTest, map, [], undefined, any, any}, one},
[],
{'merge', 3},
2,
[
{seqType, {funTest, map, [], undefined, any, any}, zero_or_many},
{seqType, {funTest, map, [], undefined, any, any}, one}
]
},
{
{qname, ?NS, ?PX, <<"put">>},
{seqType, {funTest, map, [], undefined, any, any}, one},
[],
{'put', 4},
3,
[
{seqType, {funTest, map, [], undefined, any, any}, one},
{seqType, 'xs:anyAtomicType', one},
{seqType, item, zero_or_many}
]
},
{
{qname, ?NS, ?PX, <<"remove">>},
{seqType, {funTest, map, [], undefined, any, any}, one},
[],
{'remove', 3},
2,
[
{seqType, {funTest, map, [], undefined, any, any}, one},
{seqType, 'xs:anyAtomicType', zero_or_many}
]
},
{{qname, ?NS, ?PX, <<"size">>}, {seqType, 'xs:integer', one}, [], {'size', 2}, 1, [
{seqType, {funTest, map, [], undefined, any, any}, one}
]}
]).
%% Tests whether a supplied map contains an entry for a given key
%% map:contains($map as map(*), $key as xs:anyAtomicType) as xs:boolean
-spec 'contains'(
xq_types:context(),
xq_types:xq_map(),
xq_types:xs_anyAtomicType()
) -> xq_types:xs_boolean().
'contains'(_Ctx, Map, Key) when is_map(Map) ->
MKey = xqerl_operators:key_val(Key),
is_map_key(MKey, Map);
'contains'(Ctx, Map, Key) ->
case ?VAL(Map) of
IMap when is_map(IMap) ->
'contains'(Ctx, IMap, Key);
_ ->
?err('XPTY0004')
end.
%% Returns a map that contains a single entry (a key-value pair).
%% map:entry($key as xs:anyAtomicType, $value as item()*) as map(*)
-spec 'entry'(
xq_types:context(),
xq_types:xs_anyAtomicType(),
[] | xq_types:sequence(xq_types:xq_item())
) -> xq_types:xq_map().
'entry'(_Ctx, Key0, Value0) ->
Key = xqerl_operators:key_val(Key0),
#{Key => {Key0, Value0}}.
%% Searches the supplied input sequence and any contained maps and arrays for
%% a map entry with the supplied key, and returns the corresponding values.
%% map:find($input as item()*, $key as xs:anyAtomicType) as array(*)
-spec 'find'(
xq_types:context(),
[] | xq_types:sequence(xq_types:xq_item()),
xq_types:xs_anyAtomicType()
) -> xq_types:xq_array().
'find'(Ctx, Input, Key) when not is_list(Input) ->
'find'(Ctx, [Input], Key);
'find'(_Ctx, Input, Key) ->
MKey = xqerl_operators:key_val(Key),
L = find1(Input, MKey),
array:from_list(L).
find1([], _Key) ->
[];
find1([H | T], Key) when is_map(H) ->
Vals = values(H),
case maps:find(Key, H) of
error ->
find1(Vals ++ T, Key);
{ok, {_, V}} ->
[V | find1(Vals ++ T, Key)]
end;
find1([H | T], Key) when ?IS_ARRAY(H) ->
find1(array:to_list(H) ++ T, Key);
find1([_ | T], Key) ->
find1(T, Key).
%% Applies a supplied function to every entry in a map, returning the
%% concatenation of the results.
%% map:for-each(
%% $map as map(*),
%% $action as function(xs:anyAtomicType, item()*) as item()*) as item()*
-spec 'for-each'(
xq_types:context(),
xq_types:xq_map(),
xq_types:xq_function()
) -> [] | xq_types:sequence(xq_types:xq_item()).
'for-each'(Ctx, [Map], F) ->
'for-each'(Ctx, Map, F);
'for-each'(_Ctx, Map, []) when is_map(Map) ->
Map;
'for-each'(Ctx, Map, Action) when is_map(Map), is_function(Action) ->
Deep = lists:map(fun({K, V}) -> Action(Ctx, K, V) end, maps:values(Map)),
lists:flatten(Deep);
'for-each'(Ctx, Map, #xqFunction{body = Function}) when is_map(Map), is_function(Function) ->
'for-each'(Ctx, Map, Function).
%% Returns the value associated with a supplied key in a given map.
%% map:get($map as map(*), $key as xs:anyAtomicType) as item()*
-spec 'get'(
xq_types:context(),
xq_types:xq_map(),
xq_types:xs_anyAtomicType()
) -> [] | xq_types:sequence(xq_types:xq_item()).
'get'(Ctx, [Map], Key) ->
'get'(Ctx, Map, Key);
'get'(_Ctx, Map, Key) when is_map(Map) ->
MKey = xqerl_operators:key_val(Key),
case Map of
#{MKey := {_, V}} -> V;
_ -> []
end;
'get'(Ctx, Map, Key) ->
case ?VAL(Map) of
IMap when is_map(IMap) ->
'get'(Ctx, IMap, Key);
_ ->
?err('XPTY0004')
end.
%% Returns a sequence containing all the keys present in a map
%% map:keys($map as map(*)) as xs:anyAtomicType*
-spec 'keys'(
xq_types:context(),
xq_types:xq_map()
) -> [] | xq_types:sequence(xq_types:xs_anyAtomicType()).
'keys'(_Ctx, Map) when is_map(Map) ->
% true keys are in position 1 in value
[K || {K, _} <- maps:values(Map)];
'keys'(Ctx, Map) ->
case ?VAL(Map) of
IMap when is_map(IMap) ->
'keys'(Ctx, IMap);
_ ->
?err('XPTY0004')
end.
%% Returns a map that combines the entries from a number of existing maps.
%% map:merge($maps as map(*)*) as map(*)
-spec 'merge'(
xq_types:context(),
[] | xq_types:sequence(xq_types:xq_map())
) -> xq_types:xq_map().
'merge'(_Ctx, []) ->
#{};
'merge'(_Ctx, Maps) when is_map(Maps) ->
Maps;
'merge'(_Ctx, Maps) ->
%% do_merge(lists:reverse(Maps), #{}).
List = maps_to_list(lists:reverse(Maps)),
%Lists = lists:append([maps:to_list(M) || M <- lists:reverse(Maps)]),
maps:from_list(List).
maps_to_list(Maps) ->
[
V
|| M <- Maps,
V <- maps:to_list(M)
].
%% map:merge($maps as map(*)*, $options as map(*)) as map(*)
-spec 'merge'(
xq_types:context(),
[] | xq_types:sequence(xq_types:xq_map()),
xq_types:xq_map()
) -> xq_types:xq_map().
'merge'(_Ctx, [], _) ->
#{};
'merge'(_Ctx, Maps, #{<<"duplicates">> := {_, Dup}}) when
Dup == <<"use-first">>; Dup == <<"use-any">>
->
%% do_merge(lists:reverse(Maps), #{});
List = maps_to_list(lists:reverse(Maps)),
maps:from_list(List);
'merge'(_Ctx, Maps, #{<<"duplicates">> := {_, Dup}}) when Dup == <<"use-last">> ->
%% do_merge(Maps, #{});
List = maps_to_list(Maps),
maps:from_list(List);
'merge'(_Ctx, Maps, #{<<"duplicates">> := {_, Dup}}) ->
lists:foldl(
fun(In, Out) ->
combine_maps(Out, In, Dup)
end,
#{},
lists:reverse(Maps)
);
'merge'(_, _, _) ->
?err('FOJS0005').
%% do_merge([H|T],Acc) ->
%% do_merge(T,maps:merge(Acc, H));
%% do_merge([],Acc) -> Acc.
%% Returns a map containing all the contents of the supplied map, but with an
%% additional entry, which replaces any existing entry for the same key.
%% map:put(
%% $map as map(*),
%% $key as xs:anyAtomicType,
%% $value as item()*) as map(*)
-spec 'put'(
xq_types:context(),
xq_types:xq_map(),
xq_types:xs_anyAtomicType(),
[] | xq_types:sequence(xq_types:xq_item())
) -> xq_types:xq_map().
'put'(_Ctx, Map, Key, Value) when is_map(Map) ->
Key1 = xqerl_operators:key_val(Key),
Map#{Key1 => {Key, Value}};
'put'(Ctx, Map, Key, Value) ->
case ?VAL(Map) of
IMap when is_map(IMap) ->
'put'(Ctx, IMap, Key, Value);
_ ->
?err('XPTY0004')
end.
%% Returns a map containing all the entries from a supplied map, except
%% those having a specified key.
%% map:remove($map as map(*), $keys as xs:anyAtomicType*) as map(*)
-spec 'remove'(
xq_types:context(),
xq_types:xq_map(),
[] | xq_types:sequence(xq_types:xs_anyAtomicType())
) -> xq_types:xq_map().
'remove'(_Ctx, Map, Keys) when is_map(Map) ->
UKeyList = lists:usort([xqerl_operators:key_val(K) || K <- xqerl_seq3:to_list(Keys)]),
lists:foldl(
fun(K, M) ->
maps:remove(K, M)
end,
Map,
UKeyList
);
'remove'(Ctx, Map, Keys) ->
case ?VAL(Map) of
IMap when is_map(IMap) ->
'remove'(Ctx, IMap, Keys);
_ ->
?err('XPTY0004')
end.
%% Returns the number of entries in the supplied map.
%% map:size($map as map(*)) as xs:integer
-spec 'size'(
xq_types:context(),
xq_types:xq_map()
) -> xq_types:xs_integer().
'size'(_Ctx, Map) when is_map(Map) ->
maps:size(Map);
'size'(Ctx, Map) ->
case ?VAL(Map) of
IMap when is_map(IMap) ->
'size'(Ctx, Map);
_ ->
?err('XPTY0004')
end.
combine_maps(Map1, Map2, _Any) when erlang:map_size(Map2) == 0 ->
Map1;
combine_maps(Map1, Map2, _Any) when erlang:map_size(Map1) == 0 ->
Map2;
combine_maps(Map1, Map2, <<"use-first">>) ->
Lists = lists:append([maps:to_list(M) || M <- [Map1, Map2]]),
maps:from_list(Lists);
combine_maps(Map1, Map2, <<"use-any">>) ->
Lists = lists:append([maps:to_list(M) || M <- [Map1, Map2]]),
maps:from_list(Lists);
combine_maps(Map1, Map2, <<"reject">>) ->
case maps:size(maps:with(maps:keys(Map2), Map1)) of
0 ->
maps:merge(Map2, Map1);
_ ->
?err('FOJS0003')
end;
combine_maps(Map1, Map2, <<"combine">>) ->
F = fun(K, V, M) ->
case maps:is_key(K, M) of
false ->
maps:put(K, V, M);
_ ->
Ov = maps:get(K, M),
Ov1 = element(2, Ov),
Ov2 = element(2, V),
Ov3 = {element(1, V), vconcat(Ov2, Ov1)},
maps:update(K, Ov3, M)
end
end,
maps:fold(F, Map1, Map2);
combine_maps(_, _, O) ->
?dbg("Options", O),
?err('FOJS0005').
vconcat(V1, V2) when is_list(V1), is_list(V2) ->
V1 ++ V2;
vconcat(V1, V2) when is_list(V2) ->
[V1 | V2];
vconcat(V1, V2) when is_list(V1) ->
V1 ++ [V2];
vconcat(V1, V2) ->
[V1, V2].
% used in deep-equal
-spec equal(
xq_types:xq_map(),
xq_types:xq_map()
) -> boolean().
equal(Map1, Map2) ->
Sz1 = ?MODULE:size([], Map1),
Sz2 = ?MODULE:size([], Map2),
case Sz1 =:= Sz2 of
true ->
K1 = keys([], Map1),
F = fun(K) ->
V1 = ?MODULE:get([], Map1, K),
V2 = ?MODULE:get([], Map2, K),
xqerl_operators:equal(V1, V2)
end,
lists:all(F, K1);
false ->
false
end.
% used in unary lookup
-spec values(xq_types:xq_map()) -> list([] | xq_types:sequence(xq_types:xq_item())).
values(Map) ->
[V || {_, V} <- maps:values(Map)].
% used in unary lookup and for-each
-spec get_matched(
xq_types:xq_map(),
list(xq_types:xs_anyAtomicType())
) -> list([] | xq_types:sequence(xq_types:xq_item())).
get_matched(Map, Keys) when is_map(Map) ->
lists:map(
fun(Key) ->
case maps:find(xqerl_operators:key_val(Key), Map) of
error ->
[];
{ok, {_, V}} ->
V
end
end,
Keys
).
-spec construct(
_,
list({xq_types:xs_anyAtomicType(), [] | xq_types:sequence(xq_types:xq_item())})
) -> xq_types:xq_map().
construct(_, KeyValList) ->
lists:foldl(
fun({Key, Val}, Map) ->
Key1 = xqerl_operators:key_val(Key),
case maps:is_key(Key1, Map) of
true ->
% no duplicates on construct
?err('XQDY0137');
_ ->
Map#{Key1 => {Key, Val}}
end
end,
#{},
KeyValList
).