1
%%% The contents of this file are subject to the Erlang Public License,
2
%%% Version 1.0, (the "License"); you may not use this file except in
3
%%% compliance with the License. You may obtain a copy of the License at
4
%%% http://www.erlang.org/license/EPL1_0.txt
6
%%% Software distributed under the License is distributed on an "AS IS"
7
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
8
%%% the License for the specific language governing rights and limitations
11
%%% The Original Code is xmerl-0.6
13
%%% The Initial Developer of the Original Code is Ericsson Telecom
14
%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson
15
%%% Telecom AB. All Rights Reserved.
17
%%% Contributor(s): ______________________________________.
19
%%%----------------------------------------------------------------------
20
%%% #0. BASIC INFORMATION
21
%%%----------------------------------------------------------------------
23
%%% File: xmerl_xpath_pred.erl
24
%%% Author : Ulf Wiger <ulf.wiger@ericsson.com>
25
%%% Description : Helper module to xmerl_xpath: XPATH predicates.
27
%%% Modules used : lists, string, xmerl_scan, xmerl_xpath
29
%%%----------------------------------------------------------------------
31
-module(xmerl_xpath_pred).
34
-author('ulf.wiger@ericsson.com').
40
%% internal functions (called via apply/3)
41
-export([boolean/1, boolean/2,
70
-include("xmerl.hrl").
71
-include("xmerl_xpath.hrl").
73
%% -record(obj, {type,
77
-define(string(X), #xmlObj{type = string,
79
-define(nodeset(X), #xmlObj{type = nodeset,
81
-define(number(X), #xmlObj{type = number,
83
-define(boolean(X), #xmlObj{type = boolean,
89
eval(Expr, C = #xmlContext{context_node = #xmlNode{pos = Pos}}) ->
91
Res = case Obj#xmlObj.type of
92
number when Obj#xmlObj.value == Pos ->
101
% io:format("eval(~p, ~p) -> ~p~n", [Expr, Pos, Res]),
118
expr({arith, Op, E1, E2}, C) ->
119
arith_expr(Op, E1, E2, C);
120
expr({comp, Op, E1, E2}, C) ->
121
comp_expr(Op, E1, E2, C);
122
expr({bool, Op, E1, E2}, C) ->
123
bool_expr(Op, E1, E2, C);
124
expr({'negative', E}, C) ->
127
expr({number, N}, _C) ->
129
expr({literal, S}, _C) ->
131
expr({function_call, F, Args}, C) ->
132
case core_function(F) of
138
%% here, we should look up the function in the context provided
139
%% by the caller, but we haven't figured this out yet.
140
exit({not_a_core_function, F})
142
expr({path, Type, PathExpr}, C) ->
143
#state{context=#xmlContext{nodeset = NS}} =
144
xmerl_xpath:eval_path(Type, PathExpr, C),
147
exit({unknown_expr, Expr}).
150
arith_expr('+', E1, E2, C) ->
151
?number(mk_number(C, E1) + mk_number(C, E2));
152
arith_expr('-', E1, E2, C) ->
153
?number(mk_number(C, E1) - mk_number(C, E2));
154
arith_expr('*', E1, E2, C) ->
155
?number(mk_number(C, E1) * mk_number(C, E2));
156
arith_expr('div', E1, E2, C) ->
157
?number(mk_number(C, E1) / mk_number(C, E2));
158
arith_expr('mod', E1, E2, C) ->
159
?number(mk_number(C, E1) rem mk_number(C, E2)).
161
comp_expr('>', E1, E2, C) ->
164
?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C));
165
comp_expr('<', E1, E2, C) ->
168
?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C));
169
comp_expr('>=', E1, E2, C) ->
172
?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C));
173
comp_expr('<=', E1, E2, C) ->
176
?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C));
177
comp_expr('=', E1, E2, C) ->
180
?boolean(compare_eq_format(N1,N2,C) == compare_eq_format(N2,N1,C));
181
comp_expr('!=', E1, E2, C) ->
184
?boolean(compare_eq_format(N1,N2,C) == compare_eq_format(N2,N1,C)).
186
bool_expr('or', E1, E2, C) ->
187
?boolean(mk_boolean(C, E1) or mk_boolean(C, E2));
188
bool_expr('and', E1, E2, C) ->
189
?boolean(mk_boolean(C, E1) and mk_boolean(C, E2)).
191
%% According to chapter 3.4 in XML Path Language ver 1.0 the format of
192
%% the compared objects are depending on the type of the other
194
%% 1. Comparisons involving node-sets is treated equally despite
195
%% of which comparancy operand is used. In this case:
196
%% - node-set comp node-set: string values are used
197
%% - node-set comp number : ((node-set string value) -> number)
198
%% - node-set comp boolean : (node-set string value) -> boolean
199
%% 2. Comparisons when neither object is a node-set and the operand
200
%% is = or != the following transformation is done before comparison:
201
%% - if one object is a boolean the other is converted to a boolean.
202
%% - if one object is a number the other is converted to a number.
203
%% - otherwise convert both to the string value.
204
%% 3. Comparisons when neither object is a node-set and the operand is
205
%% <=, <, >= or > both objects are converted to a number.
206
compare_eq_format(N1=#xmlObj{type=T1},N2=#xmlObj{type=T2},C) when T1==nodeset;
208
compare_nseq_format(N1,N2,C);
209
compare_eq_format(N1=#xmlObj{type=T1},#xmlObj{type=T2},C) when T1==boolean;
212
compare_eq_format(N1=#xmlObj{type=T1},#xmlObj{type=T2},C) when T1==number;
215
compare_eq_format(N1,_,C) ->
216
mk_string(C,string_value(N1)).
218
compare_ineq_format(N1=#xmlObj{type=T1},
219
N2=#xmlObj{type=T2},C) when T1==nodeset;
221
compare_nseq_format(N1,N2,C);
222
compare_ineq_format(N1,_N2,C) ->
225
compare_nseq_format(N1=#xmlObj{type = number},_N2,C) ->
227
compare_nseq_format(N1=#xmlObj{type = boolean},_N2,C) ->
229
compare_nseq_format(N1=#xmlObj{type = string},_N2,C) ->
231
compare_nseq_format(N1=#xmlObj{type = nodeset},_N2=#xmlObj{type=number},C) ->
232
%% transform nodeset value to its string-value
233
mk_number(C,string_value(N1));
234
compare_nseq_format(N1=#xmlObj{type = nodeset},_N2=#xmlObj{type=boolean},C) ->
236
compare_nseq_format(N1=#xmlObj{type = nodeset},_N2,C) ->
237
mk_string(C,string_value(N1)).
240
core_function('last') -> true;
241
core_function('position') -> true;
242
core_function('count') -> true;
243
core_function('id') -> true;
244
core_function('local-name') -> true;
245
core_function('namespace-uri') -> true;
246
core_function('name') -> true;
247
core_function('string') -> true;
248
core_function('concat') -> true;
249
core_function('starts-with') -> true;
250
core_function('contains') -> true;
251
core_function('substring-before') -> true;
252
core_function('substring-after') -> true;
253
core_function('string-length') -> true;
254
core_function('normalize-space') -> true;
255
core_function('translate') -> true;
256
core_function('boolean') -> true;
257
core_function('not') -> {true, fn_not};
258
core_function('true') -> {true, fn_true};
259
core_function('false') -> {true, fn_false};
260
core_function('lang') -> true;
261
core_function('number') -> true;
262
core_function('sum') -> true;
263
core_function('floor') -> true;
264
core_function('ceiling') -> true;
265
core_function('round') -> true;
270
%%% node set functions
273
last(#xmlContext{nodeset = Set}, []) ->
274
?number(length(Set)).
276
%% number: position()
277
position(#xmlContext{context_node = #xmlNode{pos = Pos}}, []) ->
280
%% number: count(node-set)
282
?number(length(mk_nodeset(C, Arg))).
284
%% node-set: id(object)
286
NS0 = [C#xmlContext.whole_document],
287
case Arg#xmlObj.type of
289
NodeSet = Arg#xmlObj.value,
293
StrVal = string_value(N),
294
TokensX = id_tokens(StrVal),
298
xmerl_xpath:axis(descendant_or_self,
300
attribute_test(Node, id, IdTokens)
301
end, C#xmlContext{nodeset = NS0}),
302
?nodeset(NewNodeSet);
304
StrVal = string_value(Arg#xmlObj.value),
305
IdTokens = id_tokens(StrVal),
308
select_on_attribute(NS0, id, Tok, AccX)
313
string:tokens(Str, " \t\n\r").
316
attribute_test(#xmlNode{node = #xmlElement{attributes = Attrs}},
318
case lists:keysearch(Key, #xmlAttribute.name, Attrs) of
319
{value, #xmlAttribute{value = V}} ->
320
lists:member(V, Vals);
324
attribute_test(_Node, _Key, _Vals) ->
327
%%% CONTINUE HERE!!!!
329
%% string: local-name(node-set?)
330
'local-name'(C, []) ->
331
local_name1(default_nodeset(C));
333
'local-name'(C, [Arg]) ->
334
local_name1(mk_nodeset(C, Arg)).
338
local_name1([#xmlElement{name = Name, nsinfo = NSI}|_]) ->
346
%% string: namespace-uri(node-set?)
347
'namespace-uri'(C, []) ->
348
ns_uri(default_nodeset(C));
350
'namespace-uri'(C, [Arg]) ->
351
ns_uri(mk_nodeset(C, Arg)).
356
ns_uri([#xmlElement{nsinfo = NSI, namespace = NS}|_]) ->
359
case lists:keysearch(Prefix, 1, NS#xmlNamespace.nodes) of
373
%% string: string(object?)
375
ns_string(default_nodeset(C));
377
string_value(mk_object(C, Arg)).
379
ns_string([Obj|_]) ->
382
string_value(#xmlObj{type=nodeset,value=[]}) ->
384
string_value(N=#xmlObj{type=nodeset}) ->
385
string_value(hd(N#xmlObj.value));
386
string_value(N=#xmlObj{}) ->
387
string_value(N#xmlObj.value);
388
%% Needed also string_value for root_nodes, elements (concatenation of
389
%% al decsendant text nodes) and attribute nodes (normalized value).
390
string_value(A=#xmlNode{type=attribute}) ->
391
#xmlAttribute{value=AttVal}=A#xmlNode.node,
393
string_value(El=#xmlNode{type=element}) ->
394
#xmlElement{content=C} = El#xmlNode.node,
395
TextValue = fun(#xmlText{value=T},_Fun) -> T;
396
(#xmlElement{content=Cont},Fun) -> Fun(Cont,Fun);
399
TextDecendants=fun(X) -> TextValue(X,TextValue) end,
400
?string(lists:flatten(lists:map(TextDecendants,C)));
401
string_value(infinity) -> ?string("Infinity");
402
string_value(neg_infinity) -> ?string("-Infinity");
403
string_value(A) when atom(A) ->
404
?string(atom_to_list(A));
405
string_value(N) when integer(N) ->
406
?string(integer_to_list(N));
407
string_value(N) when float(N) ->
408
N1 = round(N * 10000000000000000),
409
?string(strip_zeroes(integer_to_list(N1))).
412
strip_zs(lists:reverse(Str), 15).
414
strip_zs([H|T], 0) ->
415
lists:reverse(T) ++ [$., H];
416
strip_zs("0" ++ T, N) ->
418
strip_zs([H|T], N) ->
419
strip_zs(T, N-1, [H]).
421
strip_zs([H|T], 0, Acc) ->
422
lists:reverse(T) ++ [$.,H|Acc];
423
strip_zs([H|T], N, Acc) ->
424
strip_zs(T, N-1, [H|Acc]).
427
%% string: concat(string, string, string*)
428
concat(C, Args = [_, _|_]) ->
429
Strings = [mk_string(C, A) || A <- Args],
430
?string(lists:concat(Strings)).
432
%% boolean: starts-with(string, string)
433
'starts-with'(C, [A1, A2]) ->
434
?boolean(lists:prefix(mk_string(C, A1), mk_string(C, A2))).
436
%% boolean: contains(string, string)
437
contains(C, [A1, A2]) ->
438
Pos = string:str(mk_string(C, A1), mk_string(C, A2)),
441
%% string: substring-before(string, string)
442
'substring-before'(C, [A1, A2]) ->
443
S1 = mk_string(C, A1),
444
S2 = mk_string(C, A2),
445
Pos = string:str(S1, S2),
446
?string(string:substr(S1, 1, Pos)).
448
%% string: substring-after(string, string)
449
'substring-after'(C, [A1, A2]) ->
450
S1 = mk_string(C, A1),
451
S2 = mk_string(C, A2),
452
case string:str(S1, S2) of
456
?string(string:substr(S1, Pos))
459
%% string: substring(string, number, number?)
460
substring(C, [A1, A2]) ->
461
S = mk_string(C, A1),
462
Pos = mk_integer(C, A2),
463
?string(string:substr(S, Pos));
464
substring(C, [A1, A2, A3]) ->
465
S = mk_string(C, A1),
466
Pos = mk_integer(C, A2),
467
Length = mk_integer(C, A3),
468
?string(string:substr(S, Pos, Length)).
471
%% number: string-length(string?)
472
'string-length'(C = #xmlContext{context_node = N}, []) ->
473
length(mk_string(C, string_value(N)));
475
'string-length'(C, [A]) ->
476
length(mk_string(C, A)).
479
%% string: normalize-space(string?)
480
'normalize-space'(C = #xmlContext{context_node = N}, []) ->
481
normalize(mk_string(C, string_value(N)));
483
'normalize-space'(C, [A]) ->
484
normalize(mk_string(C, A)).
487
%% string: translate(string, string, string)
488
translate(C, [A1, A2, A3]) ->
489
S1 = mk_string(C, A1),
490
S2 = mk_string(C, A2),
491
S3 = mk_string(C, A3),
492
?string(translate1(S1, translations(S2, S3))).
494
translate1([H|T], Xls) ->
495
case lists:keysearch(H, 1, Xls) of
496
{value, {_, remove}} ->
498
{value, {_, replace, H1}} ->
499
[H1|translate1(T, Xls)];
501
[H|translate1(T, Xls)]
506
translations([H|T], [H1|T1]) ->
507
[{H, replace, H1}|translations(T, T1)];
508
translations(Rest, []) ->
509
[{X, remove} || X <- Rest];
510
translations([], _Rest) ->
515
%% boolean: boolean(object)
517
?boolean(mk_boolean(C, Arg)).
519
%% boolean: not(boolean) ->
521
?boolean(not(mk_boolean(C, Arg))).
523
%% boolean: true() ->
527
%% boolean: false() ->
531
%% boolean: lang(string) ->
532
lang(C = #xmlContext{context_node = N}, [Arg]) ->
533
S = mk_string(C, Arg),
536
#xmlElement{language = L} -> L;
537
#xmlAttribute{language = L} -> L;
538
#xmlText{language = L} -> L;
539
#xmlComment{language = L} -> L;
546
?boolean(match_lang(upcase(S), upcase(Lang)))
550
upcase([H|T]) when H >= $a, H =< $z ->
551
[H+($A-$a)|upcase(T)];
557
match_lang([H|T], [H|T1]) ->
559
match_lang([], "-" ++ _) ->
561
match_lang([], []) ->
568
%% number: number(object)
569
number(C = #xmlContext{context_node = N}, []) ->
570
?number(mk_number(C, string(C, N)));
572
?number(mk_number(C, Arg)).
576
NS = mk_nodeset(C, Arg),
579
Sum + mk_number(C, string(C, N))
583
Num = mk_number(C, Arg),
585
Num1 when Num1 > Num ->
592
Num = mk_number(C, Arg),
594
Num1 when Num1 < Num ->
602
case mk_number(C, Arg) of
612
select_on_attribute([E = #xmlElement{attributes = Attrs}|T], K, V, Acc) ->
613
case lists:keysearch(K, #xmlAttribute.name, Attrs) of
614
{value, #xmlAttribute{value = V}} ->
615
select_on_attribute(T, K, V, [E|Acc]);
617
select_on_attribute(T, K, V, Acc)
619
select_on_attribute([], _K, _V, Acc) ->
625
mk_nodeset(_C0, #xmlContext{nodeset = NS}) ->
627
mk_nodeset(_C0, #xmlObj{type = nodeset, value = NS}) ->
629
mk_nodeset(C0, Expr) ->
630
case expr(Expr, C0) of
631
#xmlObj{type = nodeset, value = NS} ->
634
exit({expected_nodeset, Other})
638
default_nodeset(#xmlContext{context_node = N}) ->
642
mk_object(_C0, Obj = #xmlObj{}) ->
644
mk_object(C0, Expr) ->
648
mk_string(_C0, #xmlObj{type = string, value = V}) ->
650
mk_string(C0, Expr) ->
651
mk_string(C0, expr(Expr, C0)).
655
mk_integer(_C0, #xmlObj{type = number, value = V}) when float(V) ->
657
mk_integer(_C0, #xmlObj{type = number, value = V}) when integer(V) ->
659
mk_integer(C, Expr) ->
660
mk_integer(C, expr(Expr, C)).
663
mk_number(_C, #xmlObj{type = string, value = V}) ->
665
mk_number(_C, #xmlObj{type = number, value = V}) ->
667
mk_number(C, N=#xmlObj{type = nodeset}) ->
668
mk_number(C,string_value(N));
669
mk_number(_C, #xmlObj{type = boolean, value = false}) ->
671
mk_number(_C, #xmlObj{type = boolean, value = true}) ->
673
mk_number(C, Expr) ->
674
mk_number(C, expr(Expr, C)).
677
mk_boolean(_C, #xmlObj{type = boolean, value = V}) ->
679
mk_boolean(_C, #xmlObj{type = number, value = 0}) ->
681
mk_boolean(_C, #xmlObj{type = number, value = V}) when float(V) ; integer(V) ->
683
mk_boolean(_C, #xmlObj{type = nodeset, value = []}) ->
685
mk_boolean(_C, #xmlObj{type = nodeset, value = _V}) ->
687
mk_boolean(_C, #xmlObj{type = string, value = []}) ->
689
mk_boolean(_C, #xmlObj{type = string, value = _V}) ->
691
mk_boolean(C, Expr) ->
692
mk_boolean(C, expr(Expr, C)).
695
normalize([H|T]) when ?whitespace(H) ->
698
ContF = fun(_ContF, RetF, _S) ->
702
#xmerl_scanner{acc_fun = fun() -> exit(acc_fun) end,
703
event_fun = fun() -> exit(event_fun) end,
704
hook_fun = fun() -> exit(hook_fun) end,
705
continuation_fun = ContF},
709
normalize(Str = [H|_], S, Acc) when ?whitespace(H) ->
710
case xmerl_scan:accumulate_whitespace(Str, S, preserve, Acc) of
711
{" " ++ Acc1, [], _S1} ->
716
normalize(T1, S1, Acc1)
718
normalize([H|T], S, Acc) ->
719
normalize(T, S, [H|Acc]);
720
normalize([], _S, Acc) ->
724
scan_number([H|T]) when ?whitespace(H) ->
726
scan_number("-" ++ T) ->
727
case catch xmerl_xpath_scan:scan_number(T) of
728
{{number, N}, Tail} ->
729
case is_all_white(Tail) of
739
case catch xmerl_xpath_scan:scan_number(T) of
740
{{number, N}, Tail} ->
741
case is_all_white(Tail) of
751
is_all_white([H|T]) when ?whitespace(H) ->
753
is_all_white([_H|_T]) ->