%%
%% %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 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(#xmlNode{pos = Pos,
node = #xmlComment{parents = Ps}}) ->
{comment, Pos, '', 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) ->
%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(#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) ->
%% 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;
{comment, #xmlComment{}} ->
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.