aboutsummaryrefslogtreecommitdiffstats
path: root/lib/xmerl/src/xmerl_xpath.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/xmerl/src/xmerl_xpath.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/xmerl/src/xmerl_xpath.erl')
-rw-r--r--lib/xmerl/src/xmerl_xpath.erl776
1 files changed, 776 insertions, 0 deletions
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 <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 docEntity() =
+%% xmlElement()
+%% | xmlAttribute()
+%% | xmlText()
+%% | xmlPI()
+%% | xmlComment()
+%% @type nodeEntity() =
+%% xmlElement()
+%% | xmlAttribute()
+%% | xmlText()
+%% | xmlPI()
+%% | xmlNamespace()
+%% | xmlDocument()
+%% @type option_list(). <p>Options allows to customize the behaviour of the
+%% XPath scanner.
+%% </p>
+%% Possible options are:
+%% <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,
+%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.