📄 xmerl_xpath.erl
字号:
%%% The contents of this file are subject to the Erlang Public License,%%% Version 1.0, (the "License"); you may not use this file except in%%% compliance with the License. You may obtain a copy of the License at%%% http://www.erlang.org/license/EPL1_0.txt%%%%%% 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.%%%%%% The Original Code is xmerl-0.13%%%%%% The Initial Developer of the Original Code is Ericsson Telecom%%% AB. Portions created by Ericsson are Copyright (C), 1998, Ericsson%%% Telecom AB. All Rights Reserved.%%%%%% Contributor(s): ______________________________________.%%%%%%----------------------------------------------------------------------%%% #0. BASIC INFORMATION%%%----------------------------------------------------------------------%%% File: xmerl_xpath.erl%%% Author : Ulf Wiger <ulf.wiger@ericsson.com>%%% Description : Implements a search engine based on XPath%%% %%% Modules used : lists, xmerl_xpath_parse, xmerl_xpath_pred, %%% xmerl_xpath_scan%%% %%%----------------------------------------------------------------------%% @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).-vsn('0.13').-date('01-02-21').-author('ulf.wiger@ericsson.com').%% 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").-record(state, {context = #xmlContext{}, acc = []}).%% -record(node, {node,%% pos,%% parents}).-define(nodeset(NS), #state{context = #xmlContext{nodeset = NS}}).-define(context(C), #state{context = C}).%% @spec string(Str, Doc) -> docEntity()%% @equiv string(Str,Doc, [])string(Str, Doc) -> string(Str, Doc, []).%% @spec string(Str,Doc,Options) -> %% docEntity()%% @equiv string(Str,Doc, [],Doc,Options)string(Str, Doc, Options) -> string(Str, Doc, [], Doc, Options).%% @spec string(Str,Node,Parents,Doc,Options) ->%% docEntity()%% Str = xPathString()%% Node = nodeEntity()%% Parents = parentList()%% Doc = nodeEntity()%% Options = option_list()%% @doc Extracts the nodes from the parsed XML tree according to XPath.string(Str, Node, Parents, Doc, Options) -> FullParents = case Parents of [] -> []; [{H, P}|_] when atom(H), 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]), [N || #xmlNode{node = N} <- NewContext#xmlContext.nodeset].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).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}).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, N}, S = #state{context = C = #xmlContext{nodeset = NS}}) -> case length(NS)>=N of true -> 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.%% @hiddenwrite_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%% @hiddeneval_path(union, {PathExpr1, PathExpr2}, C = #xmlContext{}) -> S = #state{context = C}, S1 = match_expr(PathExpr1, S), NewNodeSet = (S#state.context)#xmlContext.nodeset, match_expr(PathExpr2, S1#state{acc = 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).%% axis(Axis,NodeTest,Context::xmlContext()) -> xmlContext()%% axis(Axis,NodeTest,Context,[])%% @hiddenaxis(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.%% @hiddenaxis(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(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 -> %io:format("node_test -> true.~n", []), [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 ->% element -> NewPs = [N|Ps], match_desc(get_content(Node), NewPs, Tok, Acc, Context); _Other -> Acc end.%match_desc(Content, Parents, Tok, Context) ->% match_desc(Content, Parents, Tok, [], Context).match_desc([E = #xmlElement{}|T], Parents, Tok, Acc, Context) -> N = #xmlNode{type = node_type(E), node = E,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -