diff options
Diffstat (limited to 'lib/xmerl/src/xmerl_xpath_pred.erl')
-rw-r--r-- | lib/xmerl/src/xmerl_xpath_pred.erl | 808 |
1 files changed, 808 insertions, 0 deletions
diff --git a/lib/xmerl/src/xmerl_xpath_pred.erl b/lib/xmerl/src/xmerl_xpath_pred.erl new file mode 100644 index 0000000000..451a09bee3 --- /dev/null +++ b/lib/xmerl/src/xmerl_xpath_pred.erl @@ -0,0 +1,808 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% Description : Helper module to xmerl_xpath: XPATH predicates. + +-module(xmerl_xpath_pred). + +%% API +-export([eval/2]). + + +%% internal functions (called via apply/3) +-export([boolean/1, boolean/2, + ceiling/2, + concat/2, + contains/2, + count/2, + floor/2, + fn_false/2, + fn_not/2, + fn_true/2, + id/2, + lang/2, + last/2, + 'local-name'/2, + 'namespace-uri'/2, + name/2, + string/2, + nodeset/1, + 'normalize-space'/2, + number/1, number/2, + position/2, + round/2, + 'starts-with'/2, + string/1, + 'string-length'/2, + substring/2, + 'substring-after'/2, + 'substring-before'/2, + sum/2, + translate/2]). +-export([core_function/1]). + +-include("xmerl.hrl"). +-include("xmerl_xpath.hrl"). + +%% -record(obj, {type, +%% value}). + + +-define(string(X), #xmlObj{type = string, + value = X}). +-define(nodeset(X), #xmlObj{type = nodeset, + value = X}). +-define(number(X), #xmlObj{type = number, + value = X}). +-define(boolean(X), #xmlObj{type = boolean, + value = X}). + + + + +eval(Expr, C = #xmlContext{context_node = #xmlNode{pos = Pos}}) -> + Obj = expr(Expr, C), + Res = case Obj#xmlObj.type of + number when Obj#xmlObj.value == Pos -> + true; + number -> + false; + boolean -> + Obj#xmlObj.value; + _ -> + mk_boolean(C, Obj) + end, +% io:format("eval(~p, ~p) -> ~p~n", [Expr, Pos, Res]), + Res. + + +string(X) -> + ?string(X). + +nodeset(X) -> + ?nodeset(X). + +number(X) -> + ?number(X). + +boolean(X) -> + ?boolean(X). + + +expr({arith, Op, E1, E2}, C) -> + arith_expr(Op, E1, E2, C); +expr({comp, Op, E1, E2}, C) -> + comp_expr(Op, E1, E2, C); +expr({bool, Op, E1, E2}, C) -> + bool_expr(Op, E1, E2, C); +expr({'negative', E}, C) -> + N = mk_number(C, E), + - N; +expr({number, N}, _C) -> + ?number(N); +expr({literal, S}, _C) -> + ?string(S); +expr({function_call, F, Args}, C) -> + case core_function(F) of + {true, F1} -> + ?MODULE:F1(C, Args); + true -> + ?MODULE:F(C, Args); + false -> + %% here, we should look up the function in the context provided + %% by the caller, but we haven't figured this out yet. + exit({not_a_core_function, F}) + end; +expr({path, Type, PathExpr}, C) -> + #state{context=#xmlContext{nodeset = NS}} = + xmerl_xpath:eval_path(Type, PathExpr, C), + ?nodeset(NS); +expr(Expr, _C) -> + exit({unknown_expr, Expr}). + + +arith_expr('+', E1, E2, C) -> + ?number(mk_number(C, E1) + mk_number(C, E2)); +arith_expr('-', E1, E2, C) -> + ?number(mk_number(C, E1) - mk_number(C, E2)); +arith_expr('*', E1, E2, C) -> + ?number(mk_number(C, E1) * mk_number(C, E2)); +arith_expr('div', E1, E2, C) -> + ?number(mk_number(C, E1) / mk_number(C, E2)); +arith_expr('mod', E1, E2, C) -> + ?number(mk_number(C, E1) rem mk_number(C, E2)). + +comp_expr('>', E1, E2, C) -> + N1 = expr(E1,C), + N2 = expr(E2,C), + ?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C)); +comp_expr('<', E1, E2, C) -> + N1 = expr(E1,C), + N2 = expr(E2,C), + ?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C)); +comp_expr('>=', E1, E2, C) -> + N1 = expr(E1,C), + N2 = expr(E2,C), + ?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C)); +comp_expr('<=', E1, E2, C) -> + N1 = expr(E1,C), + N2 = expr(E2,C), + ?boolean(compare_ineq_format(N1,N2,C) > compare_ineq_format(N2,N1,C)); +comp_expr('=', E1, E2, C) -> + N1 = expr(E1,C), + N2 = expr(E2,C), + ?boolean(compare_eq_format(N1,N2,C) == compare_eq_format(N2,N1,C)); +comp_expr('!=', E1, E2, C) -> + N1 = expr(E1,C), + N2 = expr(E2,C), + ?boolean(compare_eq_format(N1,N2,C) /= compare_eq_format(N2,N1,C)). + +bool_expr('or', E1, E2, C) -> + ?boolean(mk_boolean(C, E1) or mk_boolean(C, E2)); +bool_expr('and', E1, E2, C) -> + ?boolean(mk_boolean(C, E1) and mk_boolean(C, E2)). + +%% According to chapter 3.4 in XML Path Language ver 1.0 the format of +%% the compared objects are depending on the type of the other +%% object. +%% 1. Comparisons involving node-sets is treated equally despite +%% of which comparancy operand is used. In this case: +%% - node-set comp node-set: string values are used +%% - node-set comp number : ((node-set string value) -> number) +%% - node-set comp boolean : (node-set string value) -> boolean +%% 2. Comparisons when neither object is a node-set and the operand +%% is = or != the following transformation is done before comparison: +%% - if one object is a boolean the other is converted to a boolean. +%% - if one object is a number the other is converted to a number. +%% - otherwise convert both to the string value. +%% 3. Comparisons when neither object is a node-set and the operand is +%% <=, <, >= or > both objects are converted to a number. +compare_eq_format(N1=#xmlObj{type=T1},N2=#xmlObj{type=T2},C) when T1==nodeset; + T2==nodeset -> + compare_nseq_format(N1,N2,C); +compare_eq_format(N1=#xmlObj{type=T1},#xmlObj{type=T2},C) when T1==boolean; + T2==boolean -> + mk_boolean(C,N1); +compare_eq_format(N1=#xmlObj{type=T1},#xmlObj{type=T2},C) when T1==number; + T2==number -> + mk_number(C,N1); +compare_eq_format(N1,_,C) -> + mk_string(C,string_value(N1)). + +compare_ineq_format(N1=#xmlObj{type=T1}, + N2=#xmlObj{type=T2},C) when T1==nodeset; + T2==nodeset -> + compare_nseq_format(N1,N2,C); +compare_ineq_format(N1,_N2,C) -> + mk_number(C,N1). + +compare_nseq_format(N1=#xmlObj{type = number},_N2,C) -> + mk_number(C,N1); +compare_nseq_format(N1=#xmlObj{type = boolean},_N2,C) -> + mk_boolean(C,N1); +compare_nseq_format(N1=#xmlObj{type = string},_N2,C) -> + mk_string(C,N1); +compare_nseq_format(N1=#xmlObj{type = nodeset},_N2=#xmlObj{type=number},C) -> + %% transform nodeset value to its string-value + mk_number(C,string_value(N1)); +compare_nseq_format(N1=#xmlObj{type = nodeset},_N2=#xmlObj{type=boolean},C) -> + mk_boolean(C,N1); +compare_nseq_format(N1=#xmlObj{type = nodeset},_N2,C) -> + mk_string(C,string_value(N1)). + + +core_function('last') -> true; +core_function('position') -> true; +core_function('count') -> true; +core_function('id') -> true; +core_function('local-name') -> true; +core_function('namespace-uri') -> true; +core_function('name') -> true; +core_function('string') -> true; +core_function('concat') -> true; +core_function('starts-with') -> true; +core_function('contains') -> true; +core_function('substring-before') -> true; +core_function('substring-after') -> true; +core_function('string-length') -> true; +core_function('normalize-space') -> true; +core_function('translate') -> true; +core_function('boolean') -> true; +core_function('not') -> {true, fn_not}; +core_function('true') -> {true, fn_true}; +core_function('false') -> {true, fn_false}; +core_function('lang') -> true; +core_function('number') -> true; +core_function('sum') -> true; +core_function('floor') -> true; +core_function('ceiling') -> true; +core_function('round') -> true; +core_function(_) -> + false. + + +%%% node set functions + +%% number: last() +last(#xmlContext{nodeset = Set}, []) -> + ?number(length(Set)). + +%% number: position() +position(#xmlContext{context_node = #xmlNode{pos = Pos}}, []) -> + ?number(Pos). + +%% number: count(node-set) +count(C, [Arg]) -> + ?number(length(mk_nodeset(C, Arg))). + +%% node-set: id(object) +id(C, [Arg]) -> + WD = C#xmlContext.whole_document, + NS0 = [WD], + Obj = mk_object(C,Arg), + case Obj#xmlObj.type of + nodeset -> + NodeSet = Obj#xmlObj.value, + IdTokens = + lists:foldl( + fun(N, AccX) -> + StrVal = string_value(N), + TokensX = id_tokens(StrVal), + TokensX ++ AccX + end, [], NodeSet), + NewNodeSet = + xmerl_xpath:axis(descendant_or_self, + fun(Node) -> + attribute_test(Node, id, IdTokens) + end, C#xmlContext{nodeset = NS0}), + ?nodeset(NewNodeSet); + _ -> + StrVal = string_value(Obj#xmlObj.value), + IdTokens = id_tokens(StrVal), + NodeSet = [(WD#xmlNode.node)#xmlDocument.content], + NewNodeSet = lists:foldl( + fun(Tok, AccX) -> + select_on_attribute(NodeSet, id, Tok, AccX) + end, [], IdTokens), + ?nodeset(NewNodeSet) + + end. + +id_tokens(Str=#xmlObj{type=string}) -> + string:tokens(Str#xmlObj.value, " \t\n\r"). +%%id_tokens(Str) when list(Str) -> +%% string:tokens(Str, " \t\n\r"). + +attribute_test(#xmlNode{node = #xmlElement{attributes = Attrs}}, + Key, Vals) -> + case lists:keysearch(Key, #xmlAttribute.name, Attrs) of + {value, #xmlAttribute{value = V}} -> + lists:member(V, Vals); + _ -> + false + end; +attribute_test(_Node, _Key, _Vals) -> + false. + +%%% CONTINUE HERE!!!! + +%% string: local-name(node-set?) +'local-name'(C, []) -> + local_name1(default_nodeset(C)); + +'local-name'(C, [Arg]) -> + local_name1(mk_nodeset(C, Arg)). + +local_name1([]) -> + ?string([]); +local_name1([#xmlNode{type=element,node=El}|_]) -> + #xmlElement{name=Name,nsinfo=NSI} = El, + local_name2(Name,NSI); +local_name1([#xmlNode{type=attribute,node=Att}|_]) -> + #xmlAttribute{name=Name,nsinfo=NSI} = Att, + local_name2(Name,NSI); +local_name1([#xmlElement{name = Name, nsinfo = NSI}|_]) -> + local_name2(Name,NSI). +local_name2(Name, NSI) -> + case NSI of + {_Prefix, Local} -> + ?string(Local); + [] -> + ?string(atom_to_list(Name)) + end. + +%% string: namespace-uri(node-set?) +'namespace-uri'(C, []) -> + ns_uri(default_nodeset(C)); + +'namespace-uri'(C, [Arg]) -> + ns_uri(mk_nodeset(C, Arg)). + + +ns_uri([]) -> + ?string([]); +ns_uri([#xmlElement{nsinfo = NSI, namespace = NS}|_]) -> + ns_uri2(NSI,NS); +ns_uri([#xmlNode{type=element,node=El}|_]) -> + #xmlElement{nsinfo=NSI, namespace = NS} = El, + ns_uri2(NSI,NS); +ns_uri([#xmlNode{type=attribute,node=Att}|_]) -> + #xmlAttribute{nsinfo=NSI, namespace = NS} = Att, + ns_uri2(NSI,NS); +ns_uri(_) -> + ?string([]). + +ns_uri2(NSI,NS) -> + case NSI of + {Prefix, _} -> + case lists:keysearch(Prefix, 1, NS#xmlNamespace.nodes) of + false -> + ?string([]); + {value, {_K, V}} -> + string_value(V) + end; + [] -> + ?string([]) + end. + +%% name(node-set) -> xmlObj{type=string} +%% The name function returns a string containing the QName of the node +%% first in document order. The representation of the QName is not +%% standardized and applications have their own format. At +%% http://xml.coverpages.org/clarkNS-980804.html (the author of XPath) +%% adopts the format "namespace URI"+"local-name" but according to +%% other sources it is more common to use the format: +%% '{'"namespace URI"'}'"local-name". This function also uses this +%% latter form. +name(C,[]) -> + name1(default_nodeset(C)); +name(C, [Arg]) -> + name1(mk_nodeset(C, Arg)). +name1([]) -> + ?string([]); +name1(NodeSet) -> + NSVal = + case ns_uri(NodeSet) of + #xmlObj{value=NSStr} when NSStr =/= [] -> + "{"++NSStr++"}"; + _ -> + "" + end, + #xmlObj{value=LocalName} = local_name1(NodeSet), + ?string(NSVal++LocalName). + + + +%%% String functions + +%% string: string(object?) +string(C, []) -> + ns_string(default_nodeset(C)); +string(C, [Arg]) -> + string_value(mk_object(C, Arg)). + +ns_string([Obj|_]) -> + string_value(Obj). + +string_value(#xmlObj{type=nodeset,value=[]}) -> + ?string(""); +string_value(N=#xmlObj{type=nodeset}) -> + string_value(hd(N#xmlObj.value)); +string_value(N=#xmlObj{}) -> + string_value(N#xmlObj.value); +%% Needed also string_value for root_nodes, elements (concatenation of +%% al decsendant text nodes) and attribute nodes (normalized value). +string_value(A=#xmlNode{type=attribute}) -> + #xmlAttribute{value=AttVal}=A#xmlNode.node, + ?string(AttVal); +string_value(El=#xmlNode{type=element}) -> + #xmlElement{content=C} = El#xmlNode.node, + TextValue = fun(#xmlText{value=T},_Fun) -> T; + (#xmlElement{content=Cont},Fun) -> Fun(Cont,Fun); + (_,_) -> [] + end, + TextDecendants=fun(X) -> TextValue(X,TextValue) end, + ?string(lists:flatten(lists:map(TextDecendants,C))); +string_value(T=#xmlNode{type=text}) -> + #xmlText{value=Txt} = T#xmlNode.node, + ?string(Txt); +string_value(infinity) -> ?string("Infinity"); +string_value(neg_infinity) -> ?string("-Infinity"); +string_value(A) when is_atom(A) -> + ?string(atom_to_list(A)); +string_value(N) when is_integer(N) -> + ?string(integer_to_list(N)); +string_value(N) when is_float(N) -> + N1 = round(N * 10000000000000000), + ?string(strip_zeroes(integer_to_list(N1))); +string_value(Str) when is_list(Str) -> + ?string(Str). + +strip_zeroes(Str) -> + strip_zs(lists:reverse(Str), 15). + +strip_zs([H|T], 0) -> + lists:reverse(T) ++ [$., H]; +strip_zs("0" ++ T, N) -> + strip_zs(T, N-1); +strip_zs([H|T], N) -> + strip_zs(T, N-1, [H]). + +strip_zs([H|T], 0, Acc) -> + lists:reverse(T) ++ [$.,H|Acc]; +strip_zs([H|T], N, Acc) -> + strip_zs(T, N-1, [H|Acc]). + + +%% string: concat(string, string, string*) +concat(C, Args = [_, _|_]) -> + Strings = [mk_string(C, A) || A <- Args], + ?string(lists:concat(Strings)). + +%% boolean: starts-with(string, string) +'starts-with'(C, [A1, A2]) -> + ?boolean(lists:prefix(mk_string(C, A2), mk_string(C, A1))). + +%% boolean: contains(string, string) +contains(C, [A1, A2]) -> + Pos = string:str(mk_string(C, A1), mk_string(C, A2)), + ?boolean(Pos > 0). + +%% string: substring-before(string, string) +'substring-before'(C, [A1, A2]) -> + S1 = mk_string(C, A1), + S2 = mk_string(C, A2), + Pos = string:str(S1, S2), + ?string(string:substr(S1, 1, Pos)). + +%% string: substring-after(string, string) +'substring-after'(C, [A1, A2]) -> + S1 = mk_string(C, A1), + S2 = mk_string(C, A2), + case string:str(S1, S2) of + 0 -> + ?string([]); + Pos -> + ?string(string:substr(S1, Pos)) + end. + +%% string: substring(string, number, number?) +substring(C, [A1, A2]) -> + S = mk_string(C, A1), + Pos = mk_integer(C, A2), + ?string(string:substr(S, Pos)); +substring(C, [A1, A2, A3]) -> + S = mk_string(C, A1), + Pos = mk_integer(C, A2), + Length = mk_integer(C, A3), + ?string(string:substr(S, Pos, Length)). + + +%% number: string-length(string?) +'string-length'(C = #xmlContext{context_node = N}, []) -> + length(mk_string(C, string_value(N))); + +'string-length'(C, [A]) -> + length(mk_string(C, A)). + + +%% string: normalize-space(string?) +'normalize-space'(C = #xmlContext{context_node = N}, []) -> + normalize(mk_string(C, string_value(N))); + +'normalize-space'(C, [A]) -> + normalize(mk_string(C, A)). + + +%% string: translate(string, string, string) +translate(C, [A1, A2, A3]) -> + S1 = mk_string(C, A1), + S2 = mk_string(C, A2), + S3 = mk_string(C, A3), + ?string(translate1(S1, translations(S2, S3))). + +translate1([H|T], Xls) -> + case lists:keysearch(H, 1, Xls) of + {value, {_, remove}} -> + translate1(T, Xls); + {value, {_, replace, H1}} -> + [H1|translate1(T, Xls)]; + false -> + [H|translate1(T, Xls)] + end; +translate1([], _) -> + []. + +translations([H|T], [H1|T1]) -> + [{H, replace, H1}|translations(T, T1)]; +translations(Rest, []) -> + [{X, remove} || X <- Rest]; +translations([], _Rest) -> + []. + + + +%% boolean: boolean(object) +boolean(C, [Arg]) -> + ?boolean(mk_boolean(C, Arg)). + +%% boolean: not(boolean) -> +fn_not(C, [Arg]) -> + ?boolean(not(mk_boolean(C, Arg))). + +%% boolean: true() -> +fn_true(_C, []) -> + ?boolean(true). + +%% boolean: false() -> +fn_false(_C, []) -> + ?boolean(false). + +%% boolean: lang(string) -> +lang(C = #xmlContext{context_node = N}, [Arg]) -> + S = mk_string(C, Arg), + Lang = + case N of + #xmlElement{language = L} -> L; + #xmlAttribute{language = L} -> L; + #xmlText{language = L} -> L; + #xmlComment{language = L} -> L; + _ -> [] + end, + case Lang of + [] -> + ?boolean(false); + _ -> + ?boolean(match_lang(upcase(S), upcase(Lang))) + end. + + +upcase([H|T]) when H >= $a, H =< $z -> + [H+($A-$a)|upcase(T)]; +upcase([H|T]) -> + [H|upcase(T)]; +upcase([]) -> + []. + +match_lang([H|T], [H|T1]) -> + match_lang(T, T1); +match_lang([], "-" ++ _) -> + true; +match_lang([], []) -> + true; +match_lang(_, _) -> + false. + + + +%% number: number(object) +number(C = #xmlContext{context_node = N}, []) -> + ?number(mk_number(C, string(C, N))); +number(C, [Arg]) -> + ?number(mk_number(C, Arg)). + + +sum(C, [Arg]) -> + NS = mk_nodeset(C, Arg), + lists:foldl( + fun(N, Sum) -> + Sum + mk_number(C, string(C, N)) + end, 0, NS). + +floor(C, [Arg]) -> + Num = mk_number(C, Arg), + case trunc(Num) of + Num1 when Num1 > Num -> + ?number(Num1-1); + Num1 -> + ?number(Num1) + end. + +ceiling(C, [Arg]) -> + Num = mk_number(C, Arg), + case trunc(Num) of + Num1 when Num1 < Num -> + ?number(Num1+1); + Num1 -> + ?number(Num1) + end. + + +round(C, [Arg]) -> + case mk_number(C, Arg) of + A when is_atom(A) -> + A; + N when is_integer(N) -> + N; + F when is_float(F) -> + round(F) + end. + + +select_on_attribute([E = #xmlElement{attributes = Attrs}|T], K, V, Acc) -> + case lists:keysearch(K, #xmlAttribute.name, Attrs) of + {value, #xmlAttribute{value = V}} -> + Acc2 = select_on_attribute(E#xmlElement.content,K,V,[E|Acc]), + select_on_attribute(T, K, V, Acc2); + _ -> + Acc2 = select_on_attribute(E#xmlElement.content,K,V,Acc), + select_on_attribute(T, K, V, Acc2) + end; +select_on_attribute([H|T], K, V, Acc) when is_record(H,xmlText) -> + select_on_attribute(T, K, V, Acc); +select_on_attribute([], _K, _V, Acc) -> + Acc. + + +%%%% + +mk_nodeset(_C0, #xmlContext{nodeset = NS}) -> + NS; +mk_nodeset(_C0, #xmlObj{type = nodeset, value = NS}) -> + NS; +mk_nodeset(C0, Expr) -> + case expr(Expr, C0) of + #xmlObj{type = nodeset, value = NS} -> + NS; + Other -> + exit({expected_nodeset, Other}) + end. + + +default_nodeset(#xmlContext{context_node = N}) -> + [N]. + + +mk_object(_C0, Obj = #xmlObj{}) -> + Obj; +mk_object(C0, Expr) -> + expr(Expr, C0). + + +mk_string(_C0, #xmlObj{type = string, value = V}) -> + V; +mk_string(C0, Obj = #xmlObj{}) -> + mk_string(C0,string_value(Obj)); +mk_string(C0, Expr) -> + mk_string(C0, expr(Expr, C0)). + + + +mk_integer(_C0, #xmlObj{type = number, value = V}) when is_float(V) -> + round(V); +mk_integer(_C0, #xmlObj{type = number, value = V}) when is_integer(V) -> + V; +mk_integer(C, Expr) -> + mk_integer(C, expr(Expr, C)). + + +mk_number(_C, #xmlObj{type = string, value = V}) -> + scan_number(V); +mk_number(_C, #xmlObj{type = number, value = V}) -> + V; +mk_number(C, N=#xmlObj{type = nodeset}) -> + mk_number(C,string_value(N)); +mk_number(_C, #xmlObj{type = boolean, value = false}) -> + 0; +mk_number(_C, #xmlObj{type = boolean, value = true}) -> + 1; +mk_number(C, Expr) -> + mk_number(C, expr(Expr, C)). + + +mk_boolean(_C, #xmlObj{type = boolean, value = V}) -> + V; +mk_boolean(_C, #xmlObj{type = number, value = 0}) -> + false; +mk_boolean(_C, #xmlObj{type = number, value = V}) when is_float(V) ; is_integer(V) -> + true; +mk_boolean(_C, #xmlObj{type = nodeset, value = []}) -> + false; +mk_boolean(_C, #xmlObj{type = nodeset, value = _V}) -> + true; +mk_boolean(_C, #xmlObj{type = string, value = []}) -> + false; +mk_boolean(_C, #xmlObj{type = string, value = _V}) -> + true; +mk_boolean(C, Expr) -> + mk_boolean(C, expr(Expr, C)). + + +normalize([H|T]) when ?whitespace(H) -> + normalize(T); +normalize(Str) -> + ContF = fun(_ContF, RetF, _S) -> + RetF() + end, + normalize(Str, + #xmerl_scanner{acc_fun = fun() -> exit(acc_fun) end, + event_fun = fun() -> exit(event_fun) end, + hook_fun = fun() -> exit(hook_fun) end, + continuation_fun = ContF}, + []). + + +normalize(Str = [H|_], S, Acc) when ?whitespace(H) -> + case xmerl_scan:accumulate_whitespace(Str, S, preserve, Acc) of + {" " ++ Acc1, [], _S1} -> + lists:reverse(Acc1); + {Acc1, [], _S1} -> + lists:reverse(Acc1); + {Acc1, T1, S1} -> + normalize(T1, S1, Acc1) + end; +normalize([H|T], S, Acc) -> + normalize(T, S, [H|Acc]); +normalize([], _S, Acc) -> + lists:reverse(Acc). + + +scan_number([H|T]) when ?whitespace(H) -> + scan_number(T); +scan_number("-" ++ T) -> + case catch xmerl_xpath_scan:scan_number(T) of + {{number, N}, Tail} -> + case is_all_white(Tail) of + true -> + N; + false -> + 'NaN' + end; + _Other -> + 'NaN' + end; +scan_number(T) -> + case catch xmerl_xpath_scan:scan_number(T) of + {{number, N}, Tail} -> + case is_all_white(Tail) of + true -> + N; + false -> + 'NaN' + end; + _Other -> + 'NaN' + end. + +is_all_white([H|T]) when ?whitespace(H) -> + is_all_white(T); +is_all_white([_H|_T]) -> + false; +is_all_white([]) -> + true. |