diff --git a/lib/xmerl/src/xmerl_ucs.erl b/lib/xmerl/src/xmerl_ucs.erl index ba450a569bec..9cee7f1e7955 100644 --- a/lib/xmerl/src/xmerl_ucs.erl +++ b/lib/xmerl/src/xmerl_ucs.erl @@ -562,7 +562,7 @@ is_incharset(In,Charset) when is_list(In) -> {error,unsupported_charset}; {error,_} -> false; - [Int] when is_integer(Int) -> + L when is_list(L) -> true end. diff --git a/lib/xmerl/src/xmerl_xpath_pred.erl b/lib/xmerl/src/xmerl_xpath_pred.erl index 99737a8f462f..d7a0896f3aa4 100644 --- a/lib/xmerl/src/xmerl_xpath_pred.erl +++ b/lib/xmerl/src/xmerl_xpath_pred.erl @@ -166,15 +166,15 @@ comp_expr('>', E1, E2, 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)); + ?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)); + ?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)); + ?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), @@ -508,8 +508,12 @@ contains(C, [A1, A2]) -> '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)). + case string:str(S1, S2) of + 0 -> + ?string([]); + Pos -> + ?string(string:substr(S1, 1, Pos - 1)) + end. %% string: substring-after(string, string) 'substring-after'(C, [A1, A2]) -> @@ -536,10 +540,10 @@ substring(C, [A1, A2, A3]) -> %% number: string-length(string?) 'string-length'(C = #xmlContext{context_node = N}, []) -> - length(mk_string(C, string_value(N))); + ?number(length(mk_string(C, string_value(N)))); 'string-length'(C, [A]) -> - length(mk_string(C, A)). + ?number(length(mk_string(C, A))). %% string: normalize-space(string?) @@ -633,17 +637,17 @@ match_lang(_, _) -> %% number: number(object) number(C = #xmlContext{context_node = N}, []) -> - ?number(mk_number(C, string(C, N))); + ?number(mk_number(C, string_value(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). + ?number(lists:foldl( + fun(N, Sum) -> + Sum + mk_number(C, string_value(N)) + end, 0, NS)). floor(C, [Arg]) -> Num = mk_number(C, Arg), @@ -665,14 +669,14 @@ ceiling(C, [Arg]) -> round(C, [Arg]) -> - case mk_number(C, Arg) of + ?number(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. + end). select_on_attribute([E = #xmlElement{attributes = Attrs}|T], K, V, Acc) -> diff --git a/lib/xmerl/test/xmerl_SUITE.erl b/lib/xmerl/test/xmerl_SUITE.erl index 0db599908610..9fa8d8d95d9b 100644 --- a/lib/xmerl/test/xmerl_SUITE.erl +++ b/lib/xmerl/test/xmerl_SUITE.erl @@ -45,7 +45,8 @@ all() -> [doctests, {group, cpd_tests}, xpath_text1, xpath_main, - xpath_abbreviated_syntax, xpath_functions, xpath_namespaces, + xpath_abbreviated_syntax, xpath_functions, xpath_relational, + xpath_namespaces, ucs_is_incharset, {group, misc}, {group, eventp_tests}, {group, ticket_tests}, {group, app_test}, {group, appup_test}, {group, format_test}]. @@ -192,6 +193,10 @@ xpath_functions(Config) -> file:set_cwd(filename:join(datadir(Config),xpath)), ok = xpath_abbrev:functions(). +xpath_relational(Config) -> + file:set_cwd(filename:join(datadir(Config),xpath)), + ok = xpath_abbrev:relational_operators(). + xpath_namespaces(Config) -> file:set_cwd(filename:join(datadir(Config),xpath)), ok = xpath_abbrev:namespaces(). @@ -673,6 +678,16 @@ allow_entities_test(Config) -> (catch xmerl_scan:file(File, [{allow_entities, false}])), ok. +%% Regression: xmerl_ucs:is_incharset/2 crashed with a case_clause on a +%% multi-character list for charsets handled via to_unicode/2 (e.g. utf-8), +%% because the clause only matched a single-element result. +ucs_is_incharset(_Config) -> + true = xmerl_ucs:is_incharset("abc", 'utf-8'), + true = xmerl_ucs:is_incharset("a", 'utf-8'), + true = xmerl_ucs:is_incharset("abc", 'iso-8859-1'), + false = xmerl_ucs:is_incharset([256, 257], 'iso-8859-1'), + ok. + %%====================================================================== %% Support Functions %%====================================================================== diff --git a/lib/xmerl/test/xmerl_SUITE_data/xpath/xpath_abbrev.erl b/lib/xmerl/test/xmerl_SUITE_data/xpath/xpath_abbrev.erl index aaa80097cd00..2b74b68eae30 100644 --- a/lib/xmerl/test/xmerl_SUITE_data/xpath/xpath_abbrev.erl +++ b/lib/xmerl/test/xmerl_SUITE_data/xpath/xpath_abbrev.erl @@ -1,13 +1,27 @@ -%%%------------------------------------------------------------------- -%%% File : xpath_abbrev.erl -%%% Author : Bertil Karlsson -%%% Description : -%%% -%%% Created : 17 Jan 2006 by Bertil Karlsson -%%%------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% SPDX-License-Identifier: Apache-2.0 +%% +%% Copyright Ericsson AB 2026. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + -module(xpath_abbrev). -export([test/0, check_node_set/2, ticket_6873/0, ticket_7496/0, functions/0]). +-export([relational_operators/0]). -export([namespaces/0]). -include_lib("common_test/include/ct.hrl"). @@ -61,7 +75,7 @@ test() -> Res22 = xmerl_xpath:string("blipp[@id and @test]",E), ok = check_node_set("blipp[@id and @test]",Res22). -check_node_set("blipp",[E1,E2,E3]) -> +check_node_set("blipp",[E1,E2,E3]) -> ok = xml_element_name(E1,blipp), ok = xml_element_name(E2,blipp), ok = xml_element_name(E3,blipp), @@ -106,7 +120,7 @@ check_node_set("/myBS_model/blipp[3]/blupp[2]",[E]) -> #xmlElement{name=blupp, attributes=[#xmlAttribute{name=att,value="bluppc2"}]}=E, ok; -check_node_set("blipp//plopp",[#xmlElement{name=plopp},#xmlElement{name=plopp}]) -> +check_node_set("blipp//plopp",[#xmlElement{name=plopp},#xmlElement{name=plopp}]) -> ok; check_node_set("//plopp",[E1,E2,E3]) -> ok = xml_element_name(E1,plopp), @@ -218,6 +232,33 @@ ticket_7496() -> ok = Test(Doc3,"//*[starts-with(local-name(),'p')]", [parent,pet,pet]). +%% Regression test for the relational operators '<', '<=' and '>='. +%% These three clauses of xmerl_xpath_pred:comp_expr/4 each carried the +%% body of '>' (a copy-paste error), so every one evaluated as 'a > b'. +%% The cases below fail on the buggy code (each selects [e,f] / [] instead +%% of the expected node sets) and pass once the operators are correct. +relational_operators() -> + Test = fun(Doc, XPath, Exp) -> + Result = xmerl_xpath:string(XPath, Doc), + Exp = [Name || #xmlElement{name = Name} <- Result], + ok + end, + {Doc,_} = xmerl_scan:string(""), + + %% Relational predicates over position() (number vs number). + ok = Test(Doc, "/a/*[position() < 3]", [b, c]), + ok = Test(Doc, "/a/*[position() <= 3]", [b, c, d]), + ok = Test(Doc, "/a/*[position() >= 3]", [d, e, f]), + ok = Test(Doc, "/a/*[position() > 3]", [e, f]), + + %% Constant relational predicates, independent of position(): each is a + %% boolean that is true, so it selects every child. + All = [b, c, d, e, f], + ok = Test(Doc, "/a/*[2 < 3]", All), + ok = Test(Doc, "/a/*[3 <= 3]", All), + ok = Test(Doc, "/a/*[3 >= 3]", All), + ok = Test(Doc, "/a/*[3 > 2]", All), + ok. functions() -> Test = fun(Doc, XPath, Exp) -> @@ -234,7 +275,7 @@ functions() -> end|| Obj <- Result], ok end, - Foo = + Foo = "" " " " Xml" @@ -257,6 +298,16 @@ functions() -> ok = Test(Doc,"/foo/bar[starts-with(name, 'X')]",[bar,bar]), ok = Test(Doc,"/foo/bar[value = string(1)]/value/text()",["1"]), + %% Regression tests: substring-before used to include the separator's + %% first character (off-by-one); string-length and round returned bare + %% numbers instead of #xmlObj{type=number}; and sum and number() (no + %% args) called string/2 with a bare node, crashing on any non-empty + %% node-set. + ok = Test(Doc,"/foo/bar[substring-before(name, 'a') = 'Xp']",[bar]), + ok = Test(Doc,"/foo/bar[string-length(name) = 3]",[bar]), + ok = Test(Doc,"/foo/bar[round(value) = 2]",[bar]), + ok = Test(Doc,"/foo[sum(bar/value) = 6]",[foo]), + ok = Test(Doc,"/foo/bar/value[number() = 2]",[value]), {Doc2,_}= xmerl_scan:file("purchaseOrder.xml"), ok = Test(Doc2,"//*[starts-with(local-name(),'c')]", @@ -266,7 +317,6 @@ functions() -> ok = Test(Doc2,"//*[starts-with(name(),'{http://www.example.com/PO1')]", ['apo:purchaseOrder','apo:comment']). - namespaces() -> {Doc,_} = xmerl_scan:file("purchaseOrder.xml", [{namespace_conformant, true}]),