From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/xmerl/src/xmerl_xpath.erl | 776 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 776 insertions(+) create mode 100644 lib/xmerl/src/xmerl_xpath.erl (limited to 'lib/xmerl/src/xmerl_xpath.erl') diff --git a/lib/xmerl/src/xmerl_xpath.erl b/lib/xmerl/src/xmerl_xpath.erl new file mode 100644 index 0000000000..182a186d2c --- /dev/null +++ b/lib/xmerl/src/xmerl_xpath.erl @@ -0,0 +1,776 @@ +%% +%% %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 : Implements a search engine based on XPath + +%% @doc The xmerl_xpath module handles the entire XPath 1.0 spec +%% XPath expressions typically occurs in XML attributes and are used to addres +%% parts of an XML document. +% The grammar is defined in xmerl_xpath_parse.yrl. +% The core functions are defined in xmerl_xpath_pred.erl. +% +%

Some useful shell commands for debugging the XPath parser

+%
+% c(xmerl_xpath_scan).
+% yecc:yecc("xmerl_xpath_parse.yrl", "xmerl_xpath_parse", true, []).
+% c(xmerl_xpath_parse).
+%
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("position() > -1")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("5 * 6 div 2")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("5 + 6 mod 2")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("5 * 6")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("-----6")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("parent::node()")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("descendant-or-self::node()")).
+% xmerl_xpath_parse:parse(xmerl_xpath_scan:tokens("parent::processing-instruction('foo')")).
+%% 
+%% +%% @type docEntity() = +%% xmlElement() +%% | xmlAttribute() +%% | xmlText() +%% | xmlPI() +%% | xmlComment() +%% @type nodeEntity() = +%% xmlElement() +%% | xmlAttribute() +%% | xmlText() +%% | xmlPI() +%% | xmlNamespace() +%% | xmlDocument() +%% @type option_list().

Options allows to customize the behaviour of the +%% XPath scanner. +%%

+%% Possible options are: +%%
+%%
{namespace, #xmlNamespace}
+%%
Set namespace nodes, from XmlNamspace, in xmlContext
+%%
{namespace, Nodes}
+%%
Set namespace nodes in xmlContext.
+%%
+ +%%
{bindings, Bs}
+%%
+%%
{functions, Fs}
+%%
+-module(xmerl_xpath). + + +%% main API +-export([string/2, + string/3, + string/5]). + +%% exported helper functions, internal for the XPath support +-export([eval_path/3, + axis/3, axis/4]). + +%% debug function +-export([write_node/1]). + + +-include("xmerl.hrl"). +-include("xmerl_internal.hrl"). + + +-record(state, {context = #xmlContext{}, + acc = []}). + + +-define(nodeset(NS), #state{context = #xmlContext{nodeset = NS}}). +-define(context(C), #state{context = C}). + + + + +%% @spec string(Str, Doc) -> [docEntity()] | Scalar +%% @equiv string(Str,Doc, []) +string(Str, Doc) -> + string(Str, Doc, []). + +%% @spec string(Str,Doc,Options) -> +%% [docEntity()] | Scalar +%% @equiv string(Str,Doc, [],Doc,Options) +string(Str, Doc, Options) -> + string(Str, Doc, [], Doc, Options). + +%% @spec string(Str,Node,Parents,Doc,Options) -> +%% [docEntity()] | Scalar +%% Str = xPathString() +%% Node = nodeEntity() +%% Parents = parentList() +%% Doc = nodeEntity() +%% Options = option_list() +%% Scalar = xmlObj +%% @doc Extracts the nodes from the parsed XML tree according to XPath. +%% xmlObj is a record with fields type and value, +%% where type is boolean | number | string +string(Str, Node, Parents, Doc, Options) -> +%% record with fields type and value, +%% where type is boolean | number | string + FullParents = + case Parents of + [] -> + []; + [{H, P}|_] when is_atom(H), is_integer(P) -> + full_parents(Parents, Doc) + end, +%io:format("string FullParents=~p~n",[FullParents]), + ContextNode=#xmlNode{type = node_type(Node), + node = Node, + parents = FullParents}, +%io:format("string ContextNode=~p~n",[ContextNode]), + WholeDoc = whole_document(Doc), +%io:format("string WholeDoc=~p~n",[WholeDoc]), + Context=(new_context(Options))#xmlContext{context_node = ContextNode, + whole_document = WholeDoc}, +%io:format("string Context=~p~n",[Context]), + #state{context = NewContext} = match(Str, #state{context = Context}), +%io:format("string NewContext=~p~n",[NewContext]), + case NewContext#xmlContext.nodeset of + ScalObj = #xmlObj{type=Scalar} + when Scalar == boolean; Scalar == number; Scalar == string -> + ScalObj; + #xmlObj{type=nodeset,value=NodeSet} -> + NodeSet; + _ -> + [N || #xmlNode{node = N} <- NewContext#xmlContext.nodeset] + end. + + +whole_document(#xmlDocument{} = Doc) -> + #xmlNode{type = root_node, + node = Doc, + parents = []}; +whole_document(Other) -> + #xmlNode{type = root_node, + node = #xmlDocument{content = Other}, + parents = []}. + + +new_context(Options) -> + new_context(Options, #xmlContext{}). + +new_context([{namespace, #xmlNamespace{nodes = Nodes}}|T], C) -> + new_context(T, C#xmlContext{namespace = ns_nodes(Nodes)}); +new_context([{namespace, Nodes}|T], C) -> + new_context(T, C#xmlContext{namespace = ns_nodes(Nodes)}); +new_context([{bindings, Bs}|T], C) -> + new_context(T, C#xmlContext{bindings = Bs}); +new_context([{functions, Fs}|T], C) -> + new_context(T, C#xmlContext{functions = Fs}); +new_context([], C) -> + C. + + +ns_nodes([{Prefix, URI}|T]) -> + [{to_string(Prefix), to_atom(URI)}|ns_nodes(T)]; +ns_nodes([]) -> + []. + +full_parents(Ps, Doc) -> + full_parents1(lists:reverse(Ps), [Doc], []). + +full_parents1([{Name, Pos}|Ns], Content, Parents) -> + E = locate_element(Name, Pos, Content), + PN = #xmlNode{type = element, + node = E, + parents = Parents}, + full_parents1(Ns, get_content(E), [PN|Parents]); +full_parents1([], _E, Parents) -> + Parents. + + +locate_element(Name, Pos, [E = #xmlElement{name = Name, pos = Pos}|_]) -> + E; +locate_element(_Name, Pos, [#xmlElement{pos = P}|_]) when P >= Pos -> + %% we've passed Pos (P > Pos) or the name is wrong (P == Pos) + exit(invalid_parents); +locate_element(_Name, _Pos, []) -> + exit(invalid_parents); +locate_element(Name, Pos, [_|T]) -> + locate_element(Name, Pos, T). + + +match(Str, S = #state{}) -> + Tokens = xmerl_xpath_scan:tokens(Str), + case xmerl_xpath_parse:parse(Tokens) of + {ok, Expr} -> + match_expr(Expr, S); + Error -> + Error + end. + + +match_expr({path, Type, Arg}, S) -> + eval_path(Type, Arg, S#state.context); +%% PrimaryExpr +match_expr(PrimExpr,S) -> + eval_primary_expr(PrimExpr,S). + + + + + +path_expr({refine, StepExpr1, StepExpr2}, S) -> + ?dbg("StepExpr1=~p StepExpr2=~p~n", [StepExpr1,StepExpr2]), + ?dbg("length(nodeset) = ~p~n", + [length((S#state.context)#xmlContext.nodeset)]), + S1 = path_expr(StepExpr1, S), + ?dbg("length(nodeset1) = ~p~n", + [length((S1#state.context)#xmlContext.nodeset)]), + path_expr(StepExpr2, S1); +path_expr({step, {Axis, NodeTest, PredExpr}}, S = #state{context = C, + acc = Acc}) -> + ?dbg("PredExpr = ~p~n", [PredExpr]), + NewContext = axis(Axis, NodeTest, C, Acc), + pred_expr(PredExpr, S#state{context = NewContext}); +path_expr('/', S) -> + S. + + +pred_expr([], S) -> + S; +pred_expr([{pred, Pred}|Preds], S = #state{}) -> + ?dbg("Pred = ~p~n", [Pred]), + NewS = eval_pred(Pred, S), + pred_expr(Preds, NewS). + +%% simple case: the predicate is a number, e.g. para[5]. +%% No need to iterate over all nodes in the nodeset; we know what to do. +%% +eval_pred({number, N0}, + S = #state{context = C = #xmlContext{nodeset = NS, + axis_type = AxisType}}) -> + Len = length(NS), + case Len>=N0 of + true -> + N = case AxisType of + forward -> + N0; + reverse -> + Len + 1 - N0 + end, + NewNodeSet = [lists:nth(N, NS)], + NewContext = C#xmlContext{nodeset = NewNodeSet}, + S#state{context = NewContext}; + false -> S#state{context = C#xmlContext{nodeset = []}} + end; +eval_pred(Predicate, S = #state{context = C = + #xmlContext{nodeset = NodeSet}}) -> + NewNodeSet = + lists:filter( + fun(Node) -> + %io:format("current node: ~p~n", [write_node(Node)]), + ThisContext = C#xmlContext{context_node = Node}, + xmerl_xpath_pred:eval(Predicate, ThisContext) + end, NodeSet), + NewContext = C#xmlContext{nodeset = NewNodeSet}, + S#state{context = NewContext}. + + + +%% write_node(Node::xmlNode()) -> {Type,Pos,Name,Parents} +%% Helper function to access essential information from the xmlNode record. +%% @hidden +write_node(#xmlNode{pos = Pos, + node = #xmlAttribute{name = Name, + parents = Ps}}) -> + {attribute, Pos, Name, Ps}; +write_node(#xmlNode{pos = Pos, + node = #xmlElement{name = Name, + parents = Ps}}) -> + {element, Pos, Name, Ps}; +write_node(#xmlNode{pos = Pos, + node = #xmlText{value = Txt, + parents = Ps}}) -> + {text, Pos, Txt, Ps}; +write_node(_) -> + other. + + +%% eval_path(Type,Arg,S::state()) -> state() +%% Eval path +%% @hidden +eval_path(union, {PathExpr1, PathExpr2}, C = #xmlContext{}) -> + S = #state{context = C}, + S1 = match_expr(PathExpr1, S), +%% NewNodeSet = (S1#state.context)#xmlContext.nodeset, + S2 = match_expr(PathExpr2, S1#state{context=C}), + NodeSet1 = (S1#state.context)#xmlContext.nodeset, + NodeSet2 = (S2#state.context)#xmlContext.nodeset, + NewNodeSet = ordsets:to_list(ordsets:union(ordsets:from_list(NodeSet1), + ordsets:from_list(NodeSet2))), + S2#state{context=(S2#state.context)#xmlContext{nodeset=NewNodeSet}}; +eval_path(abs, PathExpr, C = #xmlContext{}) -> + NodeSet = [C#xmlContext.whole_document], + Context = C#xmlContext{nodeset = NodeSet}, + S = #state{context = Context}, + path_expr(PathExpr, S); +eval_path(rel, PathExpr, C = #xmlContext{}) -> + NodeSet = [C#xmlContext.context_node], + Context = C#xmlContext{nodeset = NodeSet}, + S = #state{context = Context}, + path_expr(PathExpr, S); +eval_path(filter, {PathExpr, PredExpr}, C = #xmlContext{}) -> + S = #state{context = C}, + S1 = path_expr(PathExpr, S), + pred_expr(PredExpr, S1). + +eval_primary_expr(FC = {function_call,_,_},S = #state{context = Context}) -> +%% NewNodeSet = xmerl_xpath_pred:eval(FC, Context), + NewNodeSet = xmerl_xpath_lib:eval(primary_expr, FC, Context), + NewContext = Context#xmlContext{nodeset = NewNodeSet}, + S#state{context = NewContext}; +eval_primary_expr(PrimExpr,_S) -> + exit({primary_expression,{not_implemented, PrimExpr}}). + + +%% axis(Axis,NodeTest,Context::xmlContext()) -> xmlContext() +%% axis(Axis,NodeTest,Context,[]) +%% @hidden +axis(Axis, NodeTest, Context) -> + axis(Axis, NodeTest, Context, []). + + +%% axis(Axis,NodeTest,Context::xmlContext(),Acc) -> xmlContext() +%% +%% An axis specifies the tree relationship between the nodes selected by +%% the location step and the context node. +%% @hidden +axis(Axis, NodeTest, Context = #xmlContext{nodeset = NS0}, Acc) -> + NewNodeSet=lists:foldr( + fun(N, AccX) -> + axis1(Axis, NodeTest, N, AccX, Context) + end, Acc, NS0), + update_nodeset(fwd_or_reverse(Axis, Context), NewNodeSet). + + +axis1(self, Tok, N, Acc, Context) -> + match_self(Tok, N, Acc, Context); +axis1(descendant, Tok, N, Acc, Context) -> + match_descendant(Tok, N, Acc, Context); +axis1(child, Tok, N, Acc, Context) -> + match_child(Tok, N, Acc, Context); +axis1(parent, Tok, N, Acc, Context) -> + match_parent(Tok, N, Acc, Context); +axis1(ancestor, Tok, N, Acc, Context) -> + match_ancestor(Tok, N, Acc, Context); +axis1(following_sibling, Tok, N, Acc, Context) -> + match_following_sibling(Tok, N, Acc, Context); +axis1(preceding_sibling, Tok, N, Acc, Context) -> + match_preceding_sibling(Tok, N, Acc, Context); +axis1(following, Tok, N, Acc, Context) -> + match_following(Tok, N, Acc, Context); +axis1(preceding, Tok, N, Acc, Context) -> + match_preceding(Tok, N, Acc, Context); +axis1(attribute, Tok, N, Acc, Context) -> + match_attribute(Tok, N, Acc, Context); +%axis1(namespace, Tok, N, Acc, Context) -> +% match_namespace(Tok, N, Acc, Context); +axis1(ancestor_or_self, Tok, N, Acc, Context) -> + match_ancestor_or_self(Tok, N, Acc, Context); +axis1(descendant_or_self, Tok, N, Acc, Context) -> + match_descendant_or_self(Tok, N, Acc, Context). + + +fwd_or_reverse(ancestor, Context) -> + reverse_axis(Context); +fwd_or_reverse(ancestor_or_self, Context) -> + reverse_axis(Context); +fwd_or_reverse(preceding_sibling, Context) -> + reverse_axis(Context); +fwd_or_reverse(preceding, Context) -> + reverse_axis(Context); +fwd_or_reverse(_, Context) -> + forward_axis(Context). + +reverse_axis(Context) -> + Context#xmlContext{axis_type = reverse}. +forward_axis(Context) -> + Context#xmlContext{axis_type = forward}. + + + +match_self(Tok, N, Acc, Context) -> + case node_test(Tok, N, Context) of + true -> + [N|Acc]; + false -> + Acc + end. + + +match_descendant(Tok, N, Acc, Context) -> + #xmlNode{parents = Ps, node = Node, type = Type} = N, + case Type of + El when El == element; El == root_node -> + NewPs = [N|Ps], + match_desc(get_content(Node), NewPs, Tok, Acc, Context); + _Other -> + Acc + end. + + +match_desc([E = #xmlElement{}|T], Parents, Tok, Acc, Context) -> + Acc1 = match_desc(T, Parents, Tok, Acc, Context), + N = #xmlNode{type = node_type(E), + node = E, + parents = Parents}, + NewParents = [N|Parents], + Acc2 = match_desc(get_content(E), NewParents, Tok, Acc1, Context), + match_self(Tok, N, Acc2, Context); +match_desc([E|T], Parents, Tok, Acc, Context) -> + Acc1 = match_desc(T, Parents, Tok, Acc, Context), + N = #xmlNode{node = E, + type = node_type(E), + parents = Parents}, + match_self(Tok, N, Acc1, Context); +match_desc([], _Parents, _Tok, Acc, _Context) -> + Acc. + + + +%% "The 'descendant-or-self' axis contains the context node and the +%% descendants of the context node." +match_descendant_or_self(Tok, N, Acc, Context) -> + Acc1 = match_descendant(Tok, N, Acc, Context), + match_self(Tok, N, Acc1, Context). + + +match_child(Tok, N, Acc, Context) -> + %io:format("match_child(~p)~n", [write_node(N)]), + #xmlNode{parents = Ps, node = Node, type = Type} = N, + case Type of + El when El == element; El == root_node -> + NewPs = [N|Ps], + lists:foldr( + fun(E, AccX) -> + ThisN = #xmlNode{type = node_type(E), + node = E, + parents = NewPs}, + match_self(Tok, ThisN, AccX, Context) + end, Acc, get_content(Node)); + _Other -> + Acc + end. + + +%% "The 'parent' axis contains the parent of the context node, +%% if there is one." +match_parent(Tok, N, Acc, Context) -> + case N#xmlNode.parents of + [] -> + Acc; + [PN|_] -> + match_self(Tok, PN, Acc, Context) + end. + + +%% "The 'ancestor' axis contains the ancestors of the context node; +%% the ancestors of the context node consists of the parent of the context +%% node and the parent's parent and so on; thus, the ancestor axis will +%% always include the root node, unless the context node is the root node." +match_ancestor(Tok, N, Acc, Context) -> + Parents = N#xmlNode.parents, + lists:foldl( + fun(PN, AccX) -> + match_self(Tok, PN, AccX, Context) + end, Acc, Parents). + + + + +%% "The 'ancestor-or-self' axis contains the context node and the ancestors +%% of the context node; thus, the acestor axis will always include the +%% root node." +match_ancestor_or_self(Tok, N, Acc, Context) -> + Acc1 = match_self(Tok, N, Acc, Context), + match_ancestor(Tok, N, Acc1, Context). + + +match_following_sibling(_Tok, #xmlAttribute{}, Acc, _Context) -> + Acc; +match_following_sibling(_Tok, #xmlNamespace{}, Acc, _Context) -> + Acc; + +match_following_sibling(Tok, N, Acc, Context) -> + #xmlNode{parents = Ps, node = Node} = N, + case Ps of + [#xmlNode{type = element, + node = #xmlElement{} = PNode}|_] -> + FollowingSiblings = lists:nthtail(get_position(Node), + get_content(PNode)), + lists:foldr( + fun(E, AccX) -> + ThisN = #xmlNode{type = node_type(E), + node = E, + parents = Ps}, + match_self(Tok, ThisN, AccX, Context) + end, Acc, FollowingSiblings); + _Other -> + Acc + end. + + +%% "The 'following' axis contains all nodes in the same document as the +%% context node that are after the context node in document order, excluding +%% any descendants and excluding attribute nodes and namespace nodes." +match_following(Tok, N, Acc, Context) -> + #xmlNode{parents = Ps, node = Node} = N, + case Ps of + [#xmlNode{type = element, + node = #xmlElement{} = PNode} = P|_] -> + FollowingSiblings = lists:nthtail(get_position(Node), + get_content(PNode)), + Acc0 = match_following(Tok, P, Acc, Context), + lists:foldr( + fun(E, AccX) -> + ThisN = #xmlNode{type = node_type(E), + node = E, + parents = Ps}, + match_descendant_or_self(Tok, ThisN, AccX, Context) + end, Acc0, FollowingSiblings); + _Other -> + Acc + end. + + +%% "The preceding-sibling axis contains all the preceding siblings of the +%% context node; if the context node is an attribute node or namespace node, +%% the preceding-sibling axis is empty." +match_preceding_sibling(_Tok, #xmlAttribute{}, Acc, _Context) -> + Acc; +match_preceding_sibling(_Tok, #xmlNamespace{}, Acc, _Context) -> + Acc; + +match_preceding_sibling(Tok, N, Acc, Context) -> + #xmlNode{parents = Ps, node = Node} = N, + case Ps of + [#xmlNode{type = element, + node = #xmlElement{} = PNode}|_] -> + PrecedingSiblings = lists:sublist(get_content(PNode), 1, + get_position(Node) - 1), + lists:foldr( + fun(E, AccX) -> + ThisN = #xmlNode{type = node_type(E), + node = E, + parents = Ps}, + match_self(Tok, ThisN, AccX, Context) + end, Acc, PrecedingSiblings); + _Other -> + Acc + end. + + +%% "The 'preceding' axis contains all nodes in the same document as the context +%% node that are before the context node in document order, exluding any +%% ancestors and excluding attribute nodes and namespace nodes." +match_preceding(Tok, N, Acc, Context) -> + #xmlNode{parents = Ps, node = Node} = N, + case Ps of + [#xmlNode{type = element, + node = #xmlElement{} = PNode} = P|_] -> + PrecedingSiblings = lists:sublist(get_content(PNode), 1, + get_position(Node) - 1), + Acc0 = lists:foldr( + fun(E, AccX) -> + ThisN = #xmlNode{type = node_type(E), + node = E, + parents = Ps}, + match_descendant_or_self(Tok, ThisN, + AccX, Context) + end, Acc, PrecedingSiblings), + match_preceding(Tok, P, Acc0, Context); + _Other -> + Acc + end. + + +%% "The 'attribute' axis contains the attributes of the context node; the +%% axis will be empty unless the context node is an element." +match_attribute(Tok, N, Acc, Context) -> + case N#xmlNode.type of + element -> + #xmlNode{parents = Ps, node = E} = N, + lists:foldr( + fun(A, AccX) -> + ThisN = #xmlNode{type = attribute, + node = A, + parents = [N|Ps]}, + match_self(Tok, ThisN, AccX, Context) + end, Acc, E#xmlElement.attributes); + _Other -> + %%[] + Acc + end. + +node_type(#xmlAttribute{}) -> attribute; +node_type(#xmlElement{}) -> element; +node_type(#xmlText{}) -> text; +node_type(#xmlPI{}) -> processing_instruction; +node_type(#xmlNamespace{}) -> namespace; +node_type(#xmlDocument{}) -> root_node. + +%% "The namespace axis contains the namespace nodes of the context node; +%% the axis will be empty unless the context node is an element." +%match_namespace(_Tok, _N, _Acc, _Context) -> + %% TODO: IMPLEMENT NAMESPACE AXIS +% erlang:fault(not_yet_implemented). + + +update_nodeset(Context = #xmlContext{axis_type = AxisType}, NodeSet) -> + MapFold = + case AxisType of + forward -> + mapfoldl; + reverse -> + mapfoldr + end, + {Result, _N} = + lists:MapFold(fun(Node, N) -> + {Node#xmlNode{pos = N}, N + 1} + end, 1, NodeSet), + Context#xmlContext{nodeset = Result}. + + + +node_test(F, N, Context) when is_function(F) -> + F(N, Context); +node_test({wildcard, _}, #xmlNode{type=ElAt}, _Context) + when ElAt==element; ElAt==attribute -> + true; +node_test({prefix_test, Prefix}, #xmlNode{node = N}, _Context) -> + case N of + #xmlElement{nsinfo = {Prefix, _}} -> true; + #xmlAttribute{nsinfo = {Prefix, _}} -> true; + _ -> + false + end; +node_test({name, {Tag, _Prefix, _Local}}, + #xmlNode{node = #xmlElement{name = Tag}}=_N, _Context) -> + %io:format("node_test({tag, ~p}, ~p) -> true.~n", [Tag, write_node(_N)]), + true; +node_test({name, {Tag, Prefix, Local}}, + #xmlNode{node = #xmlElement{name = Name, + expanded_name = EExpName, + nsinfo = {_Prefix1, _} + }}, Context) -> + case expanded_name(Prefix, Local, Context) of + [] -> + Res = (Tag == Name), + ?dbg("node_test(~p, ~p) -> ~p.~n", + [{Tag, Prefix, Local}, write_node(Name), Res]), + Res; + ExpName -> + Res = (ExpName == EExpName), + ?dbg("node_test(~p, ~p) -> ~p.~n", + [{Tag, Prefix, Local}, write_node(Name), Res]), + Res + end; +node_test({name, {_Tag, Prefix, Local}}, + #xmlNode{node = #xmlElement{name = Name, + expanded_name = _EExpName, + namespace = NS + }}, Context) -> + case expanded_name(Prefix, Local, Context) of + [] -> + ?dbg("node_test(~p, ~p) -> ~p.~n", + [{_Tag, Prefix, Local}, write_node(Name), false]), + false; + ExpName -> + Res = (ExpName == {NS#xmlNamespace.default,Name}), + ?dbg("node_test(~p, ~p) -> ~p.~n", + [{_Tag, Prefix, Local}, write_node(Name), Res]), + Res + end; +node_test({name, {Tag,_Prefix,_Local}}, + #xmlNode{node = #xmlAttribute{name = Tag}}, _Context) -> + true; +node_test({name, {_Tag, Prefix, Local}}, + #xmlNode{node = #xmlAttribute{expanded_name = {URI, Local}, + nsinfo = {_Prefix1, _}, + namespace = NS}}, _Context) -> + NSNodes = NS#xmlNamespace.nodes, + case lists:keysearch(Prefix, 1, NSNodes) of + {value, {_, URI}} -> + ?dbg("node_test(~, ~p) -> true.~n", + [{_Tag, Prefix, Local}, write_node(NSNodes)]), + true; + false -> + ?dbg("node_test(~, ~p) -> false.~n", + [{_Tag, Prefix, Local}, write_node(NSNodes)]), + false + end; +node_test({node_type, NT}, #xmlNode{node = N}, _Context) -> + case {NT, N} of + {text, #xmlText{}} -> + true; + {node, _} -> + true; + {attribute, #xmlAttribute{}} -> + true; + {namespace, #xmlNamespace{}} -> + true; + _ -> + false + end; +node_test({processing_instruction, {literal, _, Name}}, + #xmlNode{node = {processing_instruction, Name, _Data}}, _Context) -> + true; +node_test(_Other, _N, _Context) -> + %io:format("node_test(~p, ~p) -> false.~n", [_Other, write_node(_N)]), + false. + + +expanded_name(Prefix, Local, #xmlContext{namespace = NS}) -> + case lists:keysearch(Prefix, 1, NS) of + {value, {_, URI}} -> + {URI, list_to_atom(Local)}; + false -> + [] + end. + + +to_atom(A) when is_atom(A) -> A; +to_atom(S) when is_list(S) -> list_to_atom(S). + +to_string(A) when is_atom(A) -> atom_to_list(A); +to_string(S) when is_list(S) -> S. + + +get_content(#xmlElement{content = C}) when is_list(C) -> + C; +get_content(#xmlElement{content = F} = E) when is_function(F) -> + case F() of + C when is_list(C) -> + C; + _Other -> + exit({bad_content, E}) + end; +get_content(#xmlDocument{content = C}) when is_list(C) -> + C; +get_content(#xmlDocument{content = C}) -> + [C]. + + +get_position(#xmlElement{pos = N}) -> + N; +get_position(#xmlText{pos = N}) -> + N. -- cgit v1.2.3