%% %% %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.