aboutsummaryrefslogblamecommitdiffstats
path: root/lib/xmerl/src/xmerl_xpath.erl
blob: bce2a199f4a630e87bd473da7c002e4524ffb1bc (plain) (tree)
1
2
3
4


                   
                                                        
















                                                                         

                                                                              



















                                                                                            
                       




                      
                   



                                                                           
      
                        
       








































































                                                                       
                                               


                                                
                                               
                                   
                                         

                                                                         
                                       
                                                                         
                                             





































































































































                                                                         
                                                                  






















                                                                           


                                                         



                                                    



                                                           


























                                                                             
                                                                 
                            

                                 
 
                                                             
                                                      
                                                                       
                                                          
                                  









































                                                                        

                                         






































































                                                                      
                                                








































































































































































                                                                               
                                          
                                        



                                                                        













































                                                                     



















                                                                       






                                                                               
                                                        
                                                         
         
                                                                
             

















                                                       




                                                                    
                                                                        




































                                                                   














                                                                
        


                                                                   







                                                           
                                    
                 

                                   

                                             


                 


                                                             
                                  
                                                                     





































                                                                
%%
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2003-2011. 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 occur in XML attributes and are used to address
%% parts of an XML document.
%     The grammar is defined in <code>xmerl_xpath_parse.yrl</code>.
%     The core functions are defined in <code>xmerl_xpath_pred.erl</code>.
%
%     <p>Some useful shell commands for debugging the XPath parser</p>
% <pre>
% 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')")).
%% </pre>
%%
%% @type nodeEntity() =
%%      xmlElement()
%%    | xmlAttribute()
%%    | xmlText() 
%%    | xmlPI()
%%    | xmlComment()
%%    | xmlNsNode()
%%    | xmlDocument()
%% @type option_list(). <p>Options allows to customize the behaviour of the
%%     XPath scanner.
%% </p>
%% <p>
%% Possible options are:
%% </p>
%% <dl>
%%  <dt><code>{namespace, #xmlNamespace}</code></dt>
%%    <dd>Set namespace nodes, from XmlNamspace, in xmlContext</dd>
%%  <dt><code>{namespace, Nodes}</code></dt>
%%    <dd>Set namespace nodes in xmlContext.</dd>
%% </dl>

%%  <dt><code>{bindings, Bs}</code></dt>
%%   <dd></dd>
%% <dt><code>{functions, Fs}</code></dt>
%%   <dd></dd>
-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,
%?dbg("string FullParents=~p~n",[FullParents]),
    ContextNode=#xmlNode{type = node_type(Node),
			 node = Node,
			 parents = FullParents},
%?dbg("string ContextNode=~p~n",[ContextNode]),
    WholeDoc = whole_document(Doc),
%?dbg("string WholeDoc=~p~n",[WholeDoc]),
    Context=(new_context(Options))#xmlContext{context_node = ContextNode,
					      whole_document = WholeDoc},
%?dbg("string Context=~p~n",[Context]),
    #state{context = NewContext} = match(Str, #state{context = Context}),
%?dbg("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) ->
		  %?dbg("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(#xmlNode{pos = Pos,
		    node = #xmlComment{parents = Ps}}) ->
    {comment, Pos, '', Ps};
write_node(#xmlNode{pos = Pos,
		    node = #xmlPI{name = Name,
				  parents = Ps}}) ->
    {processing_instruction, Pos, Name, Ps};
write_node(#xmlNode{pos = Pos,
		    node = #xmlNsNode{parents = Ps,
				      prefix = Prefix}}) ->
    {namespace, Pos, Prefix, 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, {pred, Pred}}, C = #xmlContext{}) ->
    S = #state{context = C},
    S1 = match_expr(PathExpr, S),
    eval_pred(Pred, S1).

eval_primary_expr(PrimExpr, S = #state{context = Context}) ->
%%    NewNodeSet = xmerl_xpath_pred:eval(FC, Context),
    NewNodeSet = xmerl_xpath_lib:eval(primary_expr, PrimExpr, Context),
    NewContext = Context#xmlContext{nodeset = NewNodeSet},
    S#state{context = NewContext}.
    

%% 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) ->
    %?dbg("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(#xmlNsNode{}) ->	namespace;
node_type(#xmlComment{}) ->	comment;
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) ->
    case N#xmlNode.type of
	element ->
	    #xmlNode{parents = Ps, node = E} = N,
	    #xmlElement{name = Name,
			namespace = NS,
			parents = EPs,
			pos = Pos} = E,
	    #xmlNamespace{default = Default, nodes = NSPairs} = NS,
	    ThisEPs = [{Name, Pos}|EPs],
	    ThisPs = [N|Ps],
	    Acc0 =
		case Default of
		    D when D =:= []; D =:= '' ->
			{[], 1};
		    URI ->
			DefaultNSNode = #xmlNsNode{parents = ThisEPs,
						   pos = 1,
						   prefix = [],
						   uri = URI},
			Node = #xmlNode{type = namespace,
					node = DefaultNSNode,
					parents = ThisPs},
			{[Node], 2}
		end,
	    {Nodes, _I} =
		lists:foldr(
		  fun ({Prefix, URI}, {AccX, I}) ->
			  NSNode = #xmlNsNode{parents = ThisEPs,
					      pos = I,
					      prefix = Prefix,
					      uri = URI},
			  ThisN = #xmlNode{pos = I,
					   type = namespace,
					   node = NSNode,
					   parents = ThisPs},
			  {[ThisN | AccX], I + 1}
		  end, Acc0, NSPairs),
	    lists:foldr(
	      fun (ThisN, AccX) ->
		      match_self(Tok, ThisN, AccX, Context)
	      end, Acc, Nodes);
	_Other ->
	    %%[]
	    Acc
    end.


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(_Test, #xmlNode{type=attribute,node=#xmlAttribute{name=xmlns}},
	  _Context) ->
    false;
node_test(_Test,
	  #xmlNode{type=attribute,node=#xmlAttribute{nsinfo={"xmlns",_Local}}},
	  _Context) ->
    false;
node_test({wildcard, _}, #xmlNode{type=ElAt}, _Context) 
  when ElAt==element; ElAt==attribute; ElAt==namespace ->
    true;
node_test({prefix_test, Prefix}, #xmlNode{node = N}, Context) ->
    case N of
	#xmlElement{nsinfo = {Prefix, _}} ->
            true;
        #xmlElement{expanded_name = {Uri, _}} ->
            case expanded_name(Prefix, "_", Context) of
                {Uri, _} ->
                    true;
                _ ->
                    false
            end;
	#xmlAttribute{nsinfo = {Prefix, _}} ->
            true;
        #xmlAttribute{expanded_name = {Uri, _}} ->
            case expanded_name(Prefix, "_", Context) of
                {Uri, _} ->
                    true;
                _ ->
                    false
            end;
	_ ->
	    false
    end;
node_test({name, {Tag, _Prefix, _Local}}, 
	  #xmlNode{node = #xmlElement{name = Tag}}=_N, _Context) -> 
    %?dbg("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{name = Name,
                                        expanded_name = EExpName
                                       }}, 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, [], Local}},
	  #xmlNode{node = #xmlNsNode{prefix = Local}}, _Context) ->
    true;
node_test({node_type, NT}, #xmlNode{node = N}, _Context) ->
    case {NT, N} of
	{text, #xmlText{}} ->
	    true;
	{node, _} ->
	    true;
	{attribute, #xmlAttribute{}} ->
	    true;
	{namespace, #xmlNsNode{}} ->
	    true;
	{comment, #xmlComment{}} ->
	    true;
	{processing_instruction, #xmlPI{}} ->
	    true;
	_ ->
	    false
    end;
node_test({processing_instruction, Name1},
	  #xmlNode{node = #xmlPI{name = Name2}}, _Context) ->
    Name1 == atom_to_list(Name2);
node_test(_Other, _N, _Context) ->
    %?dbg("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.