Parnell Springmeyer avatar Parnell Springmeyer committed 4f58566

Adding in initial libs

Comments (0)

Files changed (4)

mochiweb_xpath.erl

+%% mochiweb_html_xpath.erl
+%% @author Pablo Polvorin
+%% created on <2008-04-29>
+%%
+%% XPath interpreter, navigate mochiweb's html structs
+%% Only a subset of xpath is implemented, see what is supported in test.erl
+-module(mochiweb_xpath).
+
+-export([execute/2,execute/3,compile_xpath/1]).
+
+%internal data
+-record(ctx, {
+        root,
+        ctx,
+        functions
+    }).
+
+
+%% @spec( string() ) -> compiled_xpath()
+compile_xpath(Expr) ->
+    mochiweb_xpath_parser:compile_xpath(Expr).
+    
+%% @doc Execute the given XPath expression against the given document, using
+%% the default set of functions. 
+%% @spec execute(XPath,Doc) -> Results
+%% @type XPath =  compiled_xpath() | string()
+%% @type Doc = node()
+%% @type Results = [node()] | binary() | boolean() | number()
+execute(XPathString,Doc) when is_list(XPathString) ->
+    XPath = mochiweb_xpath_parser:compile_xpath(XPathString),
+    execute(XPath,Doc);
+
+execute(XPath,Root) ->
+    execute(XPath,Root,[]).
+
+%% @doc Execute the given XPath expression against the given document, 
+%%      using the default set of functions plus the user-supplied ones. 
+%%
+%% @see mochiweb_xpath_functions.erl to see how to write functions
+%%
+%% @spec execute(XPath,Doc,Functions) -> Results
+%% @type XPath =  compiled_xpath() | string()
+%% @type Doc = node()
+%% @type Functions = [FunctionDefinition]
+%% @type FunctionDefinition = {FunName,Fun,Signature}
+%% @type FunName = atom()
+%% @type Fun = fun/2
+%% @type Signature = [ArgType]
+%% @type ArgType = node_set | string | number | boolean
+%% @type Results = [node()] | binary() | boolean() | number()
+%% TODO: should pass the user-defined functions when compiling
+%%       the xpath expression (compile_xpath/1). Then the 
+%%       compiled expression would have all its functions 
+%%       resolved, and no function lookup would occur when
+%%       the expression is executed
+execute(XPathString,Doc,Functions) when is_list(XPathString) ->
+    XPath = mochiweb_xpath_parser:compile_xpath(XPathString),
+    execute(XPath,Doc,Functions);
+
+execute(XPath,Doc,Functions) ->    
+    R = {root,none,[Doc]},
+    Funs =  lists:foldl(fun(T={Key,_Fun,_Signature},Prev) ->
+                            lists:keystore(Key,1,Prev,T)
+            end,mochiweb_xpath_functions:default_functions(),Functions),
+    execute_expr(XPath,#ctx{ctx=[R],root=R,functions=Funs}).
+
+
+
+execute_expr({path,'abs',Path},Ctx =#ctx{root=Root}) ->
+    do_path_expr(Path,Ctx#ctx{ctx=[Root]});
+
+execute_expr({path,'rel',Path},Ctx) ->
+    do_path_expr(Path,Ctx);
+
+execute_expr({comp,Comp,A,B},Ctx) ->
+    CompFun = comp_fun(Comp),
+    L = execute_expr(A,Ctx),
+    R = execute_expr(B,Ctx),
+    comp(CompFun,L,R);
+
+execute_expr({literal,L},_Ctx) ->
+    [L];
+
+execute_expr({number,N},_Ctx) ->
+    [N];
+
+execute_expr({bool, Bool, Comp, Fun},Ctx) ->
+    CompF = execute_expr(Comp, Ctx),
+    FunF  = execute_expr(Fun, Ctx),
+    
+    case Bool of
+        'and' ->
+            CompF and FunF;
+        'or'  ->
+            CompF or  FunF;
+        'xor' ->
+            CompF xor FunF
+    end;
+
+execute_expr({function_call,Fun,Args},Ctx=#ctx{functions=Funs}) ->
+    RealArgs = lists:map(fun(Arg) ->
+                            execute_expr(Arg,Ctx)
+                        end,Args),
+    case lists:keysearch(Fun,1,Funs) of
+        {value,{Fun,F,FormalSignature}} -> 
+            TypedArgs = lists:map(fun({Type,Arg}) ->
+                                    mochiweb_xpath_utils:convert(Arg,Type)
+                        end,lists:zip(FormalSignature,RealArgs)),
+            F(Ctx,TypedArgs);
+        false -> 
+            throw({efun_not_found,Fun})
+    end.
+
+do_path_expr({step,{Axis,NodeTest,Predicates}},Ctx=#ctx{ctx=Context}) ->
+    NewNodeList = axis(Axis,NodeTest,Context),
+    apply_predicates(Predicates,NewNodeList,Ctx);
+
+do_path_expr({refine,Step1,Step2},Ctx) ->
+    S1 = do_path_expr(Step1,Ctx),
+    do_path_expr(Step2,Ctx#ctx{ctx=S1}).
+
+
+axis('child',{name,{Tag,_,_}},Context) ->
+    F = fun ({Tag2,_,_}) when Tag2 == Tag -> true;
+             (_) -> false
+        end,
+    N = lists:map(fun ({_,_,Childs}) -> 
+                       lists:filter(F,Childs) ;
+                   (_) -> []
+                end, Context),
+    lists:flatten(N);
+
+
+axis('child',{node_type,text},Context) ->
+    L = lists:map(fun ({_,_,Childs}) -> 
+                     case lists:filter(fun is_binary/1,Childs) of
+                            [] -> [];
+                            T -> list_to_binary(T)
+                     end;
+                       (_) -> 
+                       []
+                    end,Context),
+    L;
+
+axis('child',{wildcard,wildcard},Context) ->
+   L = lists:map(fun
+                ({_,_,Children})-> Children;
+                (_) -> []
+              end, Context),
+   lists:flatten(L);
+                    
+
+axis(attribute,{name,{Attr,_Prefix,_Local}},Context) ->
+     L = lists:foldl(fun ({_,Attrs,_},Acc) -> 
+                     case proplists:get_value(Attr,Attrs) of
+                            undefined -> Acc;
+                            V -> [V|Acc]
+                     end;
+                       (_,Acc) -> 
+                       Acc
+                    end,[],Context),
+    lists:reverse(L);
+
+axis('descendant_or_self',{node_type,'node'},Context) ->
+    descendant_or_self(Context);
+
+axis('self',{node_type,'node'},Context) ->
+    Context.
+
+%%FIXME:The order of the result is wrong, it doesn't return the nodes in
+%%      document order. Actually the problem isn't here, but in
+%%      axis('child',{Tag,_,_},Ctx). We may need to find a better strategy
+%%      to implement the child axis if document order is important
+descendant_or_self(Ctx) ->
+    L = descendant_or_self(Ctx,[]),
+    lists:reverse(L).
+
+descendant_or_self([],Acc) ->
+    Acc;
+
+descendant_or_self([E={_,_,Children}|Rest],Acc) ->
+    N = descendant_or_self(Children,[E|Acc]),
+    descendant_or_self(Rest,N);
+   
+%% text() nodes aren't included
+descendant_or_self([_|Rest],Acc) ->
+    descendant_or_self(Rest,Acc).
+
+
+apply_predicates(Predicates,NodeList,Ctx) ->
+    lists:foldl(fun(Pred,Nodes) -> 
+                 apply_predicate(Pred,Nodes,Ctx) 
+                end, NodeList,Predicates).
+
+% special case: indexing
+apply_predicate({pred,{number,N}},NodeList,_Ctx) when length(NodeList) >= N ->
+    [lists:nth(N,NodeList)];
+
+apply_predicate({pred,Pred},NodeList,Ctx) ->
+    Filter = fun(Node) ->
+                mochiweb_xpath_utils:boolean_value(
+                        execute_expr(Pred,Ctx#ctx{ctx=[Node]}))
+              end,
+    L = lists:filter(Filter,NodeList),
+    L.
+
+
+%% @see http://www.w3.org/TR/1999/REC-xpath-19991116 , section 3.4 
+comp(CompFun,L,R) when is_list(L), is_list(R) ->
+    lists:any(fun(LeftValue) ->
+                     lists:any(fun(RightValue)->
+                                 CompFun(LeftValue,RightValue) 
+                               end, R)
+              end, L);
+comp(CompFun,L,R) when is_list(L) ->
+    lists:any(fun(LeftValue) -> CompFun(LeftValue,R) end,L);
+comp(CompFun,L,R) when is_list(R) ->
+    lists:any(fun(RightValue) -> CompFun(L,RightValue) end,R);
+comp(CompFun,L,R) ->
+    CompFun(L,R).
+
+comp_fun('=') -> 
+    fun 
+        (A,B) when is_number(A) -> A == mochiweb_xpath_utils:number_value(B);
+        (A,B) when is_number(B) -> mochiweb_xpath_utils:number_value(A) == B;
+        (A,B) when is_boolean(A) -> A == mochiweb_xpath_utils:boolean_value(B);
+        (A,B) when is_boolean(B) -> mochiweb_xpath_utils:boolean_value(A) == B;
+        (A,B) -> mochiweb_xpath_utils:string_value(A) == mochiweb_xpath_utils:string_value(B)
+    end;
+
+comp_fun('!=') ->
+    fun(A,B) -> F = comp_fun('='),
+                not F(A,B) 
+    end;
+
+comp_fun('>') ->
+  fun(A,B) -> 
+    mochiweb_xpath_utils:number_value(A) > mochiweb_xpath_utils:number_value(B) 
+  end;
+comp_fun('<') ->
+  fun(A,B) -> 
+    mochiweb_xpath_utils:number_value(A) < mochiweb_xpath_utils:number_value(B)
+   end;
+comp_fun('<=') ->
+  fun(A,B) -> 
+    mochiweb_xpath_utils:number_value(A) =< mochiweb_xpath_utils:number_value(B) 
+  end;
+comp_fun('>=') ->
+  fun(A,B) -> 
+    mochiweb_xpath_utils:number_value(A) >= mochiweb_xpath_utils:number_value(B) 
+  end.

mochiweb_xpath_functions.erl

+%% xpath_functions.erl
+%% @author Pablo Polvorin 
+%% @doc Some core xpath functions that can be used in xpath expressions
+%% created on 2008-05-07
+-module(mochiweb_xpath_functions).
+
+-export([default_functions/0]).
+
+%% Default functions.
+%% The format is: {FunctionName, fun(), FunctionSignature}
+%% @type FunctionName = atom()
+%% @type FunctionSignature = [XPathType]
+%% @type XPathType = node_set | string | number | boolean
+%% 
+%% The engine is responsable of calling the function with
+%% the correct arguments, given the function signature. 
+default_functions() ->
+    [
+        {'count',fun count/2,[node_set]},
+        {'name',fun 'name'/2,[node_set]},
+        {'starts-with', fun 'starts-with'/2,[string,string]},
+        {'substring', fun substring/2,[string,number,number]},
+        {'sum', fun sum/2,[node_set]},
+        {'string-length', fun 'string-length'/2,[string]}
+    ].
+
+
+%% @doc Function: number count(node-set) 
+%%      The count function returns the number of nodes in the 
+%%      argument node-set.
+count(_Ctx,[NodeList]) ->
+    length(NodeList).
+
+%% @doc Function: string name(node-set?)
+'name'(_Ctx,[[{Tag,_,_}|_]]) ->
+    Tag.
+
+%% @doc Function: boolean starts-with(string, string) 
+%%      The starts-with function returns true if the first argument string
+%%      starts with the second argument string, and otherwise returns false.
+'starts-with'(_Ctx,[Left,Right]) ->
+    Size = size(Right),
+    case Left of
+        <<Right:Size/binary,_/binary>> -> true;
+        _ -> false
+    end.
+
+%% @doc Function: string substring(string, number, number?) 
+%%      The substring function returns the substring of the first argument 
+%%      starting at the position specified in the second argument with length
+%%      specified in the third argument
+substring(_Ctx,[String,Start,Length]) when is_binary(String)->
+    Before = Start -1,
+    After = size(String) - Before - Length,
+    case (Start + Length) =< size(String) of
+        true ->
+            <<_:Before/binary,R:Length/binary,_:After/binary>> = String,
+            R;
+        false ->
+            <<>>
+    end.
+
+%% @doc Function: number sum(node-set) 
+%%      The sum function returns the sum, for each node in the argument 
+%%      node-set, of the result of converting the string-values of the node
+%%      to a number.
+sum(_Ctx,[Values]) ->
+    lists:sum(lists:map(fun  mochiweb_xpath_utils:number_value/1,Values)).
+
+%% @doc Function: number string-length(string?) 
+%%      The string-length returns the number of characters in the string 
+%%      TODO: this isn't true: currently it returns the number of bytes
+%%            in the string, that isn't the same 
+'string-length'(_Ctx,[String]) ->
+    size(String).

mochiweb_xpath_parser.erl

+%% @author Pablo Polvorin 
+%% @doc Compile XPath expressions.
+%% This module uses the xpath parser of xmerl.. that interface isn't documented
+%% so could change between OTP versions.. its know to work with OTP R12B2 
+%% created on 2008-05-07
+-module(mochiweb_xpath_parser).
+
+-export([compile_xpath/1]).
+
+%% Return a compiled XPath expression
+compile_xpath(XPathString) ->
+    {ok,XPath} = xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens(XPathString)),
+    simplify(XPath).
+
+
+
+%% @doc Utility functions to convert between the *internal* representation of
+%       xpath expressions used in xmerl(using lists and atoms), to a
+%       representation using only binaries, to match the way in
+%       which the mochiweb html parser represents data 
+simplify({path,Type,Path}) ->
+    {path,Type,simplify_path(Path)};
+simplify({comp,Comp,A,B}) ->
+    {comp,Comp,simplify(A),simplify(B)};
+simplify({literal,L}) ->
+    {literal,list_to_binary(L)};
+simplify({number,N}) ->
+    {number,N};
+simplify({bool, Bool, C, F}) ->
+    {bool, Bool, simplify(C), simplify(F)};
+simplify({function_call,Fun,Args}) ->
+    {function_call,Fun,lists:map(fun simplify/1,Args)}.
+
+simplify_path({step,{Axis,NodeTest,Predicates}}) ->
+    {step,{Axis,
+            simplify_node_test(NodeTest),
+            simplify_predicates(Predicates)}};
+simplify_path({refine,Path1,Path2}) ->
+    {refine,simplify_path(Path1),simplify_path(Path2)}.
+
+simplify_node_test({name,{Tag,Prefix,Local}}) ->
+    {name,{to_binary(Tag),Prefix,Local}};
+
+simplify_node_test(A={node_type,Type}) when Type == 'text' ;
+                                            Type == 'node' ->
+    A;
+simplify_node_test(A={wildcard,wildcard}) ->
+    A.
+
+simplify_predicates(X) -> lists:map(fun simplify_predicate/1,X).
+simplify_predicate({pred,Pred}) ->
+    {pred,simplify(Pred)}.
+
+to_binary(X) when is_atom(X) -> list_to_binary(atom_to_list(X)).

mochiweb_xpath_utils.erl

+%% xpath_utils.erl
+%% @author Pablo Polvorin 
+%% @doc Utility functions, mainly for type conversion
+%%      Conversion rules taken from http://www.w3.org/TR/1999/REC-xpath-19991116
+%% created on 2008-05-07
+-module(mochiweb_xpath_utils).
+
+-export([string_value/1,
+        number_value/1,
+        node_set_value/1,
+        boolean_value/1,
+        convert/2]).
+
+
+string_value(N) when is_list(N)->
+    case N of
+        [X|_] -> string_value(X);
+        [] -> <<>>
+    end;
+string_value({_,_,Contents}) ->
+    L = lists:filter(fun
+                    ({_,_,_}) ->false;
+                    (B) when is_binary(B) -> true
+        end,Contents),
+    list_to_binary(L);
+
+string_value(N) when is_integer(N) ->
+    list_to_binary(integer_to_list(N));
+
+string_value(B) when is_binary(B) ->
+    B;
+string_value(B) when is_atom(B) ->
+    list_to_binary(atom_to_list(B)).
+
+node_set_value(N) when is_list(N) ->
+    N;
+node_set_value(N) ->
+    throw({node_set_expected,N}).
+
+number_value(N) when is_integer(N) or is_float(N) ->
+    N;
+
+number_value(N) when is_binary(N)->
+    String = binary_to_list(N),
+    case erl_scan:string(String) of
+        {ok, [{integer,1,I}],1} -> I;
+        {ok, [{float,1,F}],1} -> F
+    end;
+    
+number_value(N) ->
+    number_value(string_value(N)).
+
+boolean_value([]) ->
+    false;
+boolean_value([_|_]) ->
+    true;
+boolean_value(N) when is_number(N) ->
+    N /= 0;
+boolean_value(B) when is_binary(B) ->
+    size(B) /= 0;
+boolean_value(B) when B == true;
+                      B == false ->
+              B.
+
+
+
+convert(Value,number) ->
+    number_value(Value);
+convert(Value,string) ->
+    string_value(Value);
+convert(Value,node_set) ->
+    node_set_value(Value).
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.