Skip to content
Browse files

Merge branch 'master' of git@github.com:trifork/erlang_protobuffs

  • Loading branch information...
2 parents 2205a00 + e7f680d commit 877fd7b9fbb967e132851c27a03f3073ae3c8425 @eriksoe eriksoe committed Jul 4, 2011
Showing with 319 additions and 0 deletions.
  1. +319 −0 src/xpath_json.erl
View
319 src/xpath_json.erl
@@ -0,0 +1,319 @@
+-module(xpath_json).
+
+-export([select/2]).
+
+-define(TEST,true).
+-export([test/0]).
+
+-record(node, {root :: #node{} | self,
+ parent :: #node{},
+ name :: term(),
+ value :: term(),
+ position :: integer(),
+ last :: integer() }).
+
+%%
+%% Given `XPathExpr' (from xmerl_xpath_parse:parse/1) and `JSON' (from mochijson2:decode/1),
+%% select the objects matching the expression.
+%%
+select(XPathExpr, JSON) ->
+ Results = eval(XPathExpr, #node{ root=self, name=root, value=JSON }),
+ [ value_of(Result) || Result <- Results ].
+
+
+
+eval({comp, OP, Exp1, Exp2}, Node) ->
+ case eval(Exp1,Node) of
+ [] -> [];
+ M1 ->
+ M2 = eval(Exp2,Node),
+ fold_join(fun(N1,N2,Acc) ->
+ V1 = value_of(N1),
+ V2 = value_of(N2),
+
+ Bool =
+ case OP of
+ '<' -> V1 < V2;
+ '>' -> V1 > V2;
+ '>=' -> V1 >= V2;
+ '<=' -> V1 =< V2;
+ '=' -> V1 == V2;
+ '!=' -> V1 /= V2
+ end,
+ [{bool, Bool}|Acc]
+ end,
+ [],
+ M1, M2)
+ end;
+
+eval({bool, OP, Exp1, Exp2}, Node) ->
+ M1 = eval(Exp1,Node),
+ M2 = eval(Exp2,Node),
+ fold_join(fun(N1,N2,Acc) ->
+ V1 = value_of(N1),
+ V2 = value_of(N2),
+ Bool =
+ case OP of
+ 'and' when is_boolean(V1), is_boolean(V2) ->
+ V1 and V2;
+ 'or' when is_boolean(V1), is_boolean(V2) ->
+ V1 or V2;
+ _ ->
+ false
+ end,
+ [{bool,Bool}|Acc]
+ end,
+ [],
+ M1, M2);
+
+eval({arith, OP, Exp1, Exp2}, Node) ->
+ M1 = eval(Exp1,Node),
+ M2 = eval(Exp2,Node),
+ fold_join(fun(N1,N2,Acc) ->
+ V1 = value_of(N1),
+ V2 = value_of(N2),
+
+ Result = case OP of
+ '+' ->
+ V1 + V2;
+ '-' ->
+ V1 - V2;
+ 'mod' ->
+ V1 rem V2;
+ 'div' ->
+ V1 / V2;
+ '*' ->
+ V1 * V2
+ end,
+
+ [{number,Result}|Acc]
+ end,
+ [], M1, M2);
+
+eval({number, _}=Number, _) ->
+ [Number];
+
+eval({path,union,{P1,P2}}, Node) ->
+ eval(P1,Node) ++ eval(P2,Node);
+
+eval({path,abs,'/'}, Node) ->
+ [root(Node)];
+
+eval({path,abs,Exp}, Node) ->
+ eval(Exp, root(Node));
+
+eval({path,rel,Exp}, Node) ->
+ eval(Exp, Node);
+
+eval({step, How}, Node) ->
+ fold_step(fun cons/2, [], How, Node);
+
+eval({refine, Q1, Q2}, Node) ->
+ Candidates = eval(Q1, Node),
+ lists:foldl(fun(Candidate=#node{},Result) ->
+ eval(Q2, Candidate) ++ Result
+ end,
+ [],
+ Candidates);
+
+
+eval({function_call, Fun, Args}, Node) ->
+ eval_function(Fun,Args,Node).
+
+eval_predicates([], _) ->
+ true;
+eval_predicates([{pred, {number, N}}|Rest], Node=#node{ position=N }) ->
+ eval_predicates(Rest,Node);
+eval_predicates([{pred, {number, _}}|_], _) ->
+ false;
+eval_predicates([{pred, X}|Rest], Node) ->
+ lists:any(fun value_of/1, eval(X, Node))
+ andalso eval_predicates(Rest,Node).
+
+%%
+%% this is fairly slow, because xmerl uses "name" and mochijson2 uses <<"name">>
+%%
+eval_nodetest({name, {AName,[],SName}}, Node) ->
+ Name = node_name(Node),
+ Name =:= AName
+ orelse Name =:= SName
+ orelse atom_to_binary(AName, utf8) =:= Name;
+
+eval_nodetest({node_type, T}, Node) ->
+ type_of(Node) =:= T;
+
+eval_nodetest({wildcard,wildcard}, _) ->
+ true.
+
+
+eval_function(last,[],#node{ last=Last }) ->
+ [{number, Last}];
+
+eval_function(position,[],#node{ position=Position }) ->
+ [{number, Position}];
+
+eval_function('node-name',[Node],_) ->
+ case node_name(Node) of
+ undefined -> [];
+ Name -> [{literal, Name}]
+ end;
+
+
+
+%%
+%%
+eval_function(true, [], _) ->
+ [{bool, true}];
+
+eval_function(false, [], _) ->
+ [{bool, false}]
+
+.
+
+
+fold_step(Fun, Acc, {Axis, NodeTest, PredicateList}, Node ) ->
+ fold_axis(Axis,
+ fun(Node0,Acc0) ->
+ case eval_nodetest(NodeTest, Node0)
+ andalso eval_predicates(PredicateList, Node0) of
+ true ->
+ Fun(Node0,Acc0);
+ false ->
+ Acc0
+ end
+ end,
+ Acc,
+ Node).
+
+
+%% fold_axis(Axis,Fun,Acc,Current) does a fold from Current along Axis
+
+fold_axis(attribute, Fun, Acc, Node) ->
+ fold_axis(child, Fun, Acc, Node);
+
+fold_axis(child, Fun, Acc, Node=#node{ value={struct, Members}}) ->
+ %Last = length(Members),
+ {Acc2,_} = lists:foldl(fun({Name,Value}=Member, {Acc0, Idx}) ->
+ Acc1 = Fun(#node{ name=Name,
+ value=Value,
+ parent=Node,
+ root=root(Node)
+ }, Acc0),
+ {Acc1, Idx+1}
+ end,
+ {Acc, 0},
+ Members),
+ Acc2;
+
+fold_axis(child, Fun, Acc, Node=#node{ name=Name, value=Members}) when is_list(Members) ->
+ Last = length(Members),
+ {Acc2,_} = lists:foldl(fun(Member, {Acc0, Idx}) ->
+ Acc1 = Fun(#node{ name=Name,
+ value=Member,
+ parent=Node,
+ position=Idx,
+ last=Last,
+ root=root(Node)
+ }, Acc0),
+ {Acc1, Idx+1}
+ end,
+ {Acc, 0},
+ Members),
+ Acc2;
+
+fold_axis(child, _, Acc, #node{ value=Value}) when not is_list(Value)->
+ Acc;
+
+fold_axis(descendant, Fun, Acc, Node) ->
+ fold_axis(child,
+ fun(C,Acc0) ->
+ Acc1=Fun(C,Acc0),
+ fold_axis(descendant, Fun,Acc1,C)
+ end,
+ Acc,
+ Node);
+
+fold_axis(descendant_or_self, Fun, Acc, Node) ->
+ Acc1 = fold_axis(descendant, Fun, Acc, Node),
+ fold_axis(self, Fun, Acc1, Node);
+
+fold_axis(self, Fun, Acc, Node) ->
+ Fun(Node, Acc);
+
+fold_axis(parent, _, Acc, #node{ parent=undefined }) ->
+ Acc;
+fold_axis(parent, Fun, Acc, #node{ parent=Parent }) ->
+ Fun(Parent, Acc);
+
+fold_axis(ancestor, _, Acc, #node{ parent=undefined }) ->
+ Acc;
+fold_axis(ancestor, Fun, Acc, #node{ parent=ParentNode }) ->
+ fold_axis(ancestor, Fun, Fun(ParentNode,Acc), ParentNode);
+
+fold_axis(ancestor_or_self, Fun, Acc, Node) ->
+ fold_axis(ancestor, Fun, Fun(Node,Acc), Node).
+
+
+type_of(T) when is_tuple(T) ->
+ element(1, T);
+type_of(_) ->
+ undefined.
+
+node_name(#node{name=Name}) ->
+ Name;
+node_name(_) ->
+ undefined.
+
+value_of(#node{value=Value}) ->
+ Value;
+value_of({number, N}) ->
+ N;
+value_of({bool, B}) when is_boolean(B) ->
+ B;
+value_of({literal, L}) when is_list(L) ->
+ L.
+
+
+root(Node=#node{root=self}) ->
+ Node;
+root(#node{root=Node=#node{}})->
+ Node.
+
+%% utility cons function
+cons(H,T) ->
+ [H|T].
+
+-ifdef(TEST).
+
+test() ->
+
+ In = mochijson2:decode(" { \"a\": [\"zero\",\"one\",2], \"b\": { \"j\":7, \"l\":true, \"k\": { \"foo\": 3, \"t\" : {\"vals\":[ 45,6,7 ], \"j\":4} } }} "),
+ io:format("~s~n", [mochijson2:encode(In)]),
+
+ {ok, Expr} = xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("//vals[. = 6]")),
+ Out = select(Expr, In),
+
+ io:format("~s~n", [mochijson2:encode(Out)]).
+
+-endif.
+
+%%
+%% Utility 2-way fold (join)
+%%
+
+fold_join(_,Acc,[],_) ->
+ Acc;
+fold_join(_,Acc,_,[]) ->
+ Acc;
+fold_join(Fun,Acc,[E1],[E2]) ->
+ Fun(E1,E2,Acc);
+fold_join(Fun,Acc,List1,List2) ->
+ lists:foldl(fun(E1,Acc0) ->
+ lists:foldl(fun(E2,Acc1) ->
+ Fun(E1,E2,Acc1)
+ end,
+ Acc0,
+ List2)
+ end,
+ Acc,
+ List1).

0 comments on commit 877fd7b

Please sign in to comment.
Something went wrong with that request. Please try again.