%%
%% %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 : Functions to export simple and complete XML forms
%%
%% @doc Functions for exporting XML data to an external format.
%%
-module(xmerl).
%-compile(export_all).
-export([export/2,
export/3,
export_content/2,
export_element/2,
export_element/3,
export_simple/2,
export_simple/3,
export_simple_element/2,
export_simple_content/2,
callbacks/1]).
-include("xmerl.hrl").
%% @spec export(Content, Callback) -> ExportedFormat
%% @equiv export(Data, Callback, [])
export(Content, Callback) ->
export(Content, Callback, []).
%% @spec export(Content, Callback, RootAttributes) -> ExportedFormat
%% Content = [Element]
%% Callback = atom()
%% RootAttributes = [XmlAttributes]
%% @doc Exports normal, well-formed XML content, using the specified
%% callback-module.
%%
Element
is any of:
%%
%% #xmlText{}
%% #xmlElement{}
%% #xmlPI{}
%% #xmlComment{}
%% #xmlDecl{}
%%
%% (See xmerl.hrl for the record definitions.)
%% Text in #xmlText{}
elements can be deep lists of
%% characters and/or binaries.
%%
%% RootAttributes
is a list of
%% #xmlAttribute{}
attributes for the #root#
%% element, which implicitly becomes the parent of the given
%% Content
. The tag-handler function for
%% #root#
is thus called with the complete exported data of
%% Content
. Root attributes can be used to specify
%% e.g. encoding or other metadata of an XML or HTML document.
%%
%% The Callback
module should contain hook functions for
%% all tags present in the data structure. A hook function must have the
%% following format:
%% Tag(Data, Attributes, Parents, E)
%% where E
is the corresponding #xmlElement{}
,
%% Data
is the already-exported contents of E
%% and Attributes
is the list of
%% #xmlAttribute{}
records of E
. Finally,
%% Parents
is the list of parent nodes of E
,
%% on the form [{ParentTag::atom(),
%% ParentPosition::integer()}]
.
%%
%% The hook function should return either the data to be exported, or
%% a tuple {'#xml-alias#', NewTag::atom()}
, or a tuple
%% {'#xml-redefine#', Content}
, where Content
%% is a content list (which can be on simple-form; see
%% export_simple/2
for details).
%%
%% A callback module can inherit definitions from other callback
%% modules, through the required function '#xml-interitance#() ->
%% [ModuleName::atom()]
.
%%
%% @see export/2
%% @see export_simple/3
export(Content, Callback, RootAttributes) when is_atom(Callback) ->
export1(Content, callbacks(Callback), RootAttributes);
export(Content, Callbacks, RootAttrs) when is_list(Callbacks) ->
export1(Content, Callbacks, RootAttrs).
%% @spec export_simple(Content, Callback) -> ExportedFormat
%% @equiv export_simple(Content, Callback, [])
export_simple(Content, Callback) ->
export_simple(Content, Callback, []).
%% @spec export_simple(Content, Callback, RootAttributes) -> ExportedFormat
%% Content = [Element]
%% Callback = atom()
%% RootAttributes = [XmlAttributes]
%% @doc Exports "simple-form" XML content, using the specified
%% callback-module.
%% Element
is any of:
%%
%% {Tag, Attributes, Content}
%% {Tag, Content}
%% Tag
%% IOString
%% #xmlText{}
%% #xmlElement{}
%% #xmlPI{}
%% #xmlComment{}
%% #xmlDecl{}
%%
%% where
%%
%% Tag = atom()
%% Attributes = [{Name, Value}]
%% Name = atom()
%% Value = IOString | atom() | integer()
%%
%% Normal-form XML elements can thus be included in the simple-form
%% representation. Note that content lists must be flat. An
%% IOString
is a (possibly deep) list of characters and/or
%% binaries.
%%
%% RootAttributes
is a list of:
%%
%% XmlAttributes = #xmlAttribute{}
%%
%%
%% See export/3
for details on the callback module and
%% the root attributes. The XML-data is always converted to normal form
%% before being passed to the callback module.
%%
%% @see export/3
%% @see export_simple/2
export_simple(Content, Callback, RootAttrs) when is_atom(Callback) ->
export_simple1(Content, callbacks(Callback), RootAttrs);
export_simple(Content, Callbacks, RootAttrs) when is_list(Callbacks) ->
export_simple1(Content, Callbacks, RootAttrs).
export_simple1(Content, Callback, RootAttrs) ->
export1(xmerl_lib:expand_content(Content), Callback, RootAttrs).
%% This exports proper XML content in root context.
export1(Content, Callbacks, RootAttrs) when is_list(Content) ->
Result = export_content(Content, Callbacks),
Attrs = xmerl_lib:expand_attributes(RootAttrs, 1, [{'#root#',1}]),
Root = #xmlElement{name = '#root#',
pos = 1,
parents = [],
attributes = Attrs},
Args = [Result, Root#xmlElement.attributes, [], Root],
tagdef('#root#',1,[],Args,Callbacks).
%% @doc Exports simple XML content directly, without further context.
export_simple_content(Content, Callback) when is_atom(Callback) ->
export_content(xmerl_lib:expand_content(Content),
callbacks(Callback));
export_simple_content(Content, Callbacks) when is_list(Callbacks) ->
export_content(xmerl_lib:expand_content(Content), Callbacks).
%% @spec export_content(Content, Callbacks) -> term()
%% Content = [Element]
%% Callback = [atom()]
%% @doc Exports normal XML content directly, without further context.
export_content([#xmlText{value = Text} | Es], Callbacks) ->
[apply_text_cb(Callbacks, Text) | export_content(Es, Callbacks)];
export_content([#xmlPI{} | Es], Callbacks) ->
export_content(Es, Callbacks);
export_content([#xmlComment{} | Es], Callbacks) ->
export_content(Es, Callbacks);
export_content([#xmlDecl{} | Es], Callbacks) ->
export_content(Es, Callbacks);
export_content([E | Es], Callbacks) ->
[export_element(E, Callbacks) | export_content(Es, Callbacks)];
export_content([], _Callbacks) ->
[].
%% @doc Exports a simple XML element directly, without further context.
export_simple_element(Content, Callback) when is_atom(Callback) ->
export_element(xmerl_lib:expand_element(Content),
callbacks(Callback));
export_simple_element(Content, Callbacks) when is_list(Callbacks) ->
export_element(xmerl_lib:expand_element(Content), Callbacks).
%% @doc Exports a normal XML element directly, without further context.
%% This is the usual DOM style parsing.
export_element(E, CB) when is_atom(CB) ->
export_element(E, callbacks(CB));
export_element(#xmlText{value = Text}, CBs) ->
apply_text_cb(CBs, Text);
export_element(E = #xmlElement{name = Tag,
pos = Pos,
attributes = Attributes,
parents = Parents,
content = Content}, CBs) ->
Data = export_content(Content, CBs),
Args = [Data, Attributes, Parents, E],
tagdef(Tag,Pos,Parents,Args,CBs);
export_element(#xmlPI{}, _CBs) ->
[];
export_element(#xmlComment{}, _CBs) ->
[];
export_element(#xmlDecl{}, _CBs) ->
[].
%% @spec export_element(E,CallbackModule,CallbackState) -> ExportedFormat
%% @doc For on-the-fly exporting during parsing (SAX style) of the XML
%% document.
export_element(E, CallbackModule, CallbackState) when is_atom(CallbackModule) ->
export_element(E, callbacks(CallbackModule), CallbackState);
export_element(#xmlText{value = Text},CallbackModule,_CallbackState) ->
%% apply_cb(CallbackModule, '#text#', '#text#', [Text,CallbackState]);
apply_text_cb(CallbackModule,Text);
export_element(E=#xmlElement{name = Tag,
pos = Pos,
parents = Parents,
attributes = Attributes,
content = Content},Callbacks,CBstate) ->
Args = [Content, Attributes,CBstate,E],
tagdef(Tag,Pos,Parents,Args,Callbacks);
export_element(#xmlPI{}, _CallbackModule, CallbackState) ->
CallbackState;
export_element(#xmlComment{},_CallbackModule, CallbackState) ->
CallbackState;
export_element(#xmlDecl{},_CallbackModule, CallbackState) ->
CallbackState.
%% A thing returned with #xml-redefine is assumed to be a content list
%% The data may be on "simple" format.
tagdef(Tag,Pos,Parents,Args,CBs) ->
case apply_tag_cb(CBs, Tag, Args) of
{'#xml-alias#', NewTag} ->
tagdef(NewTag,Pos,Parents,Args,CBs);
{'#xml-redefine#', Data} ->
export_content(xmerl_lib:expand_content(Data, Pos, Parents),
CBs);
Other ->
Other
end.
%% @spec callbacks(Module) -> Result
%% Module = atom()
%% Result = [atom()]
%% @doc Find the list of inherited callback modules for a given module.
callbacks(Module) ->
Result = check_inheritance(Module, []),
%%% io:format("callbacks = ~p~n", [lists:reverse(Result)]),
lists:reverse(Result).
callbacks([M|Mods], Visited) ->
case lists:member(M, Visited) of
false ->
NewVisited = check_inheritance(M, Visited),
callbacks(Mods, NewVisited);
true ->
exit({cyclic_inheritance, {M, hd(Visited)}})
end;
callbacks([], Visited) ->
Visited.
check_inheritance(M, Visited) ->
%%% io:format("calling ~p:'#xml-inheritance#'()~n", [M]),
case M:'#xml-inheritance#'() of
[] ->
[M|Visited];
Mods ->
callbacks(Mods, [M|Visited])
end.
apply_text_cb(Ms, Text) ->
apply_cb(Ms, '#text#', '#text#', [Text]).
apply_tag_cb(Ms, F, Args) ->
apply_cb(Ms, F, '#element#', Args).
apply_cb(Ms, F, Df, Args) ->
apply_cb(Ms, F, Df, Args, Ms).
apply_cb([M|Ms], F, Df, Args, Ms0) ->
case catch apply(M, F, Args) of
{'EXIT', {undef,[{M,F,_}|_]}} ->
apply_cb(Ms, F, Df, Args, Ms0);
{'EXIT', Reason} ->
exit(Reason);
Res ->
Res
end;
apply_cb([], Df, Df, Args, _Ms0) ->
exit({unknown_tag, {Df, Args}});
apply_cb([], F, Df, Args, Ms0) ->
apply_cb(Ms0, Df, Df, [F|Args]).