1%% xpath_functions.erl
2%% @author Pablo Polvorin
3%% @doc Some core xpath functions that can be used in xpath expressions
4%% created on 2008-05-07
5-module(mochiweb_xpath_functions).
6
7-export([lookup_function/1]).
8
9
10%% Default functions.
11%% The format is: {FunctionName, fun(), FunctionSignature}
12%% WildCard argspec must be the last spec in list.
13%%
14%% @type FunctionName = atom()
15%% @type FunctionSignature = [XPathArgSpec]
16%% @type XPathArgSpec = XPathType | WildCardArgSpec
17%% @type WildCardArgSpec = {'*', XPathType}
18%% @type XPathType = node_set | string | number | boolean
19%%
20%% The engine is responsable of calling the function with
21%% the correct arguments, given the function signature.
22-spec lookup_function(atom()) -> mochiweb_xpath:xpath_fun_spec() | false.
23lookup_function('last') ->
24    {'last',fun last/2,[]};
25lookup_function('position') ->
26    {'position',fun position/2,[]};
27lookup_function('count') ->
28    {'count',fun count/2,[node_set]};
29lookup_function('concat') ->
30    {'concat',fun concat/2,[{'*', string}]};
31lookup_function('name') ->
32    {'name',fun 'name'/2,[node_set]};
33lookup_function('starts-with') ->
34    {'starts-with', fun 'starts-with'/2,[string,string]};
35lookup_function('contains') ->
36    {'contains', fun 'contains'/2,[string,string]};
37lookup_function('substring') ->
38    {'substring', fun substring/2,[string,number,number]};
39lookup_function('sum') ->
40    {'sum', fun sum/2,[node_set]};
41lookup_function('string-length') ->
42    {'string-length', fun 'string-length'/2,[string]};
43lookup_function('not') ->
44    {'not', fun x_not/2, [boolean]};
45lookup_function('string') ->
46    {'string', fun 'string'/2, [node_set]};
47lookup_function(_) ->
48    false.
49
50%% @doc Function: boolean last()
51%%      The position function returns the position of the current node
52last({ctx, _, _, _, Position, Size} = _Ctx, []) ->
53    Position =:= Size.
54
55%% @doc Function: number position()
56%%      The position function returns the position of the current node
57position({ctx, _, _, _, Position, _} = _Ctx, []) ->
58    Position.
59
60%% @doc Function: number count(node-set)
61%%      The count function returns the number of nodes in the
62%%      argument node-set.
63count(_Ctx,[NodeList]) ->
64    length(NodeList).
65
66%% @doc Function: concat(binary, binary, ...)
67%%      Concatenate string arguments (variable length)
68concat(_Ctx, BinariesList) ->
69    %% list_to_binary()
70    << <<Str/binary>> || Str <- BinariesList>>.
71
72%% @doc Function: string name(node-set?)
73'name'(_Ctx,[[{Tag,_,_,_}|_]]) ->
74    Tag.
75
76%% @doc Function: boolean starts-with(string, string)
77%%      The starts-with function returns true if the first argument string
78%%      starts with the second argument string, and otherwise returns false.
79'starts-with'(_Ctx,[Left,Right]) ->
80    Size = size(Right),
81    case Left of
82        <<Right:Size/binary,_/binary>> -> true;
83        _ -> false
84    end.
85
86%% @doc Function: checks that Where contains What
87contains(_Ctx,[Where, What]) ->
88    case binary:match(Where, [What]) of
89        nomatch ->
90            false;
91        {_, _} ->
92            true
93    end.
94
95%% @doc Function: string substring(string, number, number?)
96%%      The substring function returns the substring of the first argument
97%%      starting at the position specified in the second argument with length
98%%      specified in the third argument
99substring(_Ctx,[String,Start,Length]) when is_binary(String)->
100    Before = Start -1,
101    After = size(String) - Before - Length,
102    case (Start + Length) =< size(String) of
103        true ->
104            <<_:Before/binary,R:Length/binary,_:After/binary>> = String,
105            R;
106        false ->
107            <<>>
108    end.
109
110%% @doc Function: number sum(node-set)
111%%      The sum function returns the sum, for each node in the argument
112%%      node-set, of the result of converting the string-values of the node
113%%      to a number.
114sum(_Ctx,[Values]) ->
115    lists:sum([mochiweb_xpath_utils:number_value(V) || V <- Values]).
116
117%% @doc Function: number string-length(string?)
118%%      The string-length returns the number of characters in the string
119%%      TODO: this isn't true: currently it returns the number of bytes
120%%            in the string, that isn't the same
121'string-length'(_Ctx,[String]) ->
122    size(String).
123
124%%  @doc Function: string string(node_set)
125%%
126%%       The sum function returns the string representation of the
127%%       nodes in a node-set. This is different from text() in that
128%%       it concatenates each bit of the text in the node along with the text in
129%%       any children nodes along the way, in order.
130%%       Note: this differs from normal xpath in that it returns a list of strings, one
131%%       for each node in the node set, as opposed to just the first node.
132'string'(_Ctx, [NodeList]) ->
133    lists:map(fun({_Elem, _Attr, Children,_Pos}) -> concat_child_text(Children, []) end, NodeList).
134
135concat_child_text([], Result) ->
136    list_to_binary(lists:reverse(Result));
137concat_child_text([{_,_,Children,_} | Rest], Result) ->
138    concat_child_text(Rest, [concat_child_text(Children, []) | Result]);
139concat_child_text([X | Rest], Result) ->
140    concat_child_text(Rest, [X | Result]).
141
142x_not(_Ctx, [Bool]) ->
143    not Bool.
144