%%
%% %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(Data, Callback) ->
    export(Data, 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.
%% <p><code>Element</code> is any of:</p>
%% <ul>
%% 	<li><code>#xmlText{}</code></li>
%%	<li><code>#xmlElement{}</code></li>
%%	<li><code>#xmlPI{}</code></li>
%%	<li><code>#xmlComment{}</code></li>
%%	<li><code>#xmlDecl{}</code></li>
%% </ul>
%% <p>(See <tt>xmerl.hrl</tt> for the record definitions.)
%% Text in <code>#xmlText{}</code> elements can be deep lists of
%% characters and/or binaries.</p>
%%
%% <p><code>RootAttributes</code> is a list of
%% <code>#xmlAttribute{}</code> attributes for the <code>#root#</code>
%% element, which implicitly becomes the parent of the given
%% <code>Content</code>. The tag-handler function for
%% <code>#root#</code> is thus called with the complete exported data of
%% <code>Content</code>. Root attributes can be used to specify
%% e.g. encoding or other metadata of an XML or HTML document.</p>
%%
%% <p>The <code>Callback</code> module should contain hook functions for
%% all tags present in the data structure. A hook function must have the
%% following format:</p>
%% <pre>    Tag(Data, Attributes, Parents, E)</pre>
%% <p>where <code>E</code> is the corresponding <code>#xmlElement{}</code>,
%% <code>Data</code> is the already-exported contents of <code>E</code>
%% and <code>Attributes</code> is the list of
%% <code>#xmlAttribute{}</code> records of <code>E</code>. Finally,
%% <code>Parents</code> is the list of parent nodes of <code>E</code>,
%% on the form <code>[{ParentTag::atom(),
%% ParentPosition::integer()}]</code>.</p>
%%
%% <p>The hook function should return either the data to be exported, or
%% a tuple <code>{'#xml-alias#', NewTag::atom()}</code>, or a tuple
%% <code>{'#xml-redefine#', Content}</code>, where <code>Content</code>
%% is a content list (which can be on simple-form; see
%% <code>export_simple/2</code> for details).</p>
%%
%% <p>A callback module can inherit definitions from other callback
%% modules, through the required function <code>'#xml-interitance#() ->
%% [ModuleName::atom()]</code>.</p>
%%
%% @see export/2
%% @see export_simple/3

export(Data, Callback, RootAttrs) when is_atom(Callback) ->
    export1(Data, callbacks(Callback), RootAttrs);
export(Data, Callbacks, RootAttrs) when is_list(Callbacks) ->
    export1(Data, Callbacks, RootAttrs).

%% @spec export_simple(Content, Callback) -> ExportedFormat
%% @equiv export_simple(Data, Callback, [])

export_simple(Data, Callback) ->
    export_simple(Data, 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.
%% <p><code>Element</code> is any of:</p>
%% <ul>
%%	<li><code>{Tag, Attributes, Content}</code></li>
%%	<li><code>{Tag, Content}</code></li>
%%	<li><code>Tag</code></li>
%%	<li><code>IOString</code></li>
%% 	<li><code>#xmlText{}</code></li>
%%	<li><code>#xmlElement{}</code></li>
%%	<li><code>#xmlPI{}</code></li>
%%	<li><code>#xmlComment{}</code></li>
%%	<li><code>#xmlDecl{}</code></li>
%% </ul>
%% <p>where</p>
%% <ul>
%%	<li><code>Tag = atom()</code></li>
%%	<li><code>Attributes = [{Name, Value}]</code></li>
%%	<li><code>Name = atom()</code></li>
%%	<li><code>Value = IOString | atom() | integer()</code></li>
%% </ul>
%% <p>Normal-form XML elements can thus be included in the simple-form
%% representation. Note that content lists must be flat. An
%% <code>IOString</code> is a (possibly deep) list of characters and/or
%% binaries.</p>
%%
%% <p><code>RootAttributes</code> is a list of:</p>
%% <ul>
%%	<li><code>XmlAttributes = #xmlAttribute{}</code></li>
%%</ul>
%%
%% <p>See <code>export/3</code> 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.</p>
%%
%% @see export/3
%% @see export_simple/2

export_simple(Data, Callback, RootAttrs) when is_atom(Callback) ->
    export_simple1(Data, callbacks(Callback), RootAttrs);
export_simple(Data, Callbacks, RootAttrs) when is_list(Callbacks) ->
    export_simple1(Data, Callbacks, RootAttrs).

export_simple1(Data, Callback, RootAttrs) ->
    export1(xmerl_lib:expand_content(Data), Callback, RootAttrs).

%% This exports proper XML content in root context.

export1(Data, Callbacks, RootAttrs) when is_list(Data) ->
    Result = export_content(Data, 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(Data, Callback) when is_atom(Callback) ->
    export_content(xmerl_lib:expand_content(Data),
		   callbacks(Callback));
export_simple_content(Data, Callbacks) when is_list(Callbacks) ->
    export_content(xmerl_lib:expand_content(Data), 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], CBs) ->
    [apply_text_cb(CBs, Text) | export_content(Es, CBs)];
export_content([#xmlPI{} | Es], CBs) ->
    export_content(Es, CBs);
export_content([#xmlComment{} | Es], CBs) ->
    export_content(Es, CBs);
export_content([#xmlDecl{} | Es], CBs) ->
    export_content(Es, CBs);
export_content([E | Es], CBs) ->
    [export_element(E, CBs) | export_content(Es, CBs)];
export_content([], _CBs) ->
    [].

%% @doc Exports a simple XML element directly, without further context.

export_simple_element(Data, Callback) when is_atom(Callback) ->
    export_element(xmerl_lib:expand_element(Data),
		   callbacks(Callback));
export_simple_element(Data, Callbacks) when is_list(Callbacks) ->
    export_element(xmerl_lib:expand_element(Data), 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,CBs,UserState) -> ExportedFormat
%% @doc For on-the-fly exporting during parsing (SAX style) of the XML
%% document. 
export_element(E, CB,CBstate) when is_atom(CB) ->
    export_element(E, callbacks(CB), CBstate);
export_element(#xmlText{value = Text},CBs,_CBstate) ->
%%    apply_cb(CBs, '#text#', '#text#', [Text,CBstate]);
    apply_text_cb(CBs,Text);
export_element(E=#xmlElement{name = Tag,
			   pos = Pos,
			   parents = Parents,
			   attributes = Attributes,
			   content = Content},CBs,CBstate) ->
    Args = [Content, Attributes,CBstate,E],
    tagdef(Tag,Pos,Parents,Args,CBs);
export_element(#xmlPI{},_CBs,CBstate) ->
    CBstate;
export_element(#xmlComment{},_CBs,CBstate) ->
    CBstate;
export_element(#xmlDecl{},_CBs,CBstate) ->
    CBstate.

%% 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(atom()) -> [atom()]
%% @doc Find the list of inherited callback modules for a given module.

callbacks(M) ->
    Result = check_inheritance(M, []),
%%%     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]).
