aboutsummaryrefslogtreecommitdiffstats
path: root/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/erl_docgen/src/docgen_edoc_xml_cb.erl')
-rw-r--r--lib/erl_docgen/src/docgen_edoc_xml_cb.erl1154
1 files changed, 1154 insertions, 0 deletions
diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
new file mode 100644
index 0000000000..90491bc007
--- /dev/null
+++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl
@@ -0,0 +1,1154 @@
+%% ``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 via the world wide web at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See
+%% the Licence for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Initial Developer of the Original Code is Ericsson AB.
+%% Portions created by Ericsson are Copyright 1999-2006, Ericsson AB.
+%% All Rights Reserved.��
+%%
+%% $Id$
+%%
+-module(docb_edoc_xml_cb).
+
+%% This is the EDoc callback module for creating DocBuilder erlref
+%% documents (man pages) in XML format, and also a DocBuilder chapter
+%% document based on "overview.edoc".
+%%
+%% Usage examples:
+%% docb_gen File
+%% docb_gen -chapter overview.edoc
+%% or (from an Erlang shell)
+%% edoc:file(File, [{layout,docb_edoc_xml_cb},{file_suffix,".xml"},
+%% {preprocess,true}]).
+%%
+%% The origin of this file is the edoc module otpsgml_layout.erl
+%% written by Richard Carlsson.
+
+-export([module/2, overview/2]).
+
+-include("xmerl.hrl").
+
+-define(NL, "\n").
+
+%%-User interface-------------------------------------------------------
+
+%% ERLREF
+module(Element, Opts) ->
+ SortP = proplists:get_value(sort_functions, Opts, true),
+ XML = layout_module(Element, SortP),
+ xmerl:export_simple([XML], docb_xmerl_xml_cb, []).
+
+%% CHAPTER
+overview(Element, _Opts) ->
+ XML = layout_chapter(Element),
+ xmerl:export_simple([XML], docb_xmerl_xml_cb, []).
+
+%%--Internal functions--------------------------------------------------
+
+layout_module(#xmlElement{name = module, content = Es}=E, SortP) ->
+ Name = get_attrval(name, E),
+ Desc = get_content(description, Es),
+ ShortDesc = text_only(get_content(briefDescription, Desc)),
+ FullDesc = otp_xmlify(get_content(fullDescription, Desc)),
+ Types0 = get_content(typedecls, Es),
+ Types1 = lists:sort([{type_name(Et), Et} || Et <- Types0]),
+ Functions =
+ case SortP of
+ true ->
+ lists:sort([{function_name(Ef), Ef} ||
+ Ef <- get_content(functions, Es)]);
+ false ->
+ [{function_name(Ef), Ef} ||
+ Ef <- get_content(functions, Es)]
+ end,
+ Header = {header, [
+ ?NL,{title, [Name]},
+ ?NL,{prepared, [""]},
+ ?NL,{responsible, [""]},
+ ?NL,{docno, ["1"]},
+ ?NL,{approved, [""]},
+ ?NL,{checked, [""]},
+ ?NL,{date, [""]},
+ ?NL,{rev, ["A"]},
+ ?NL,{file, [Name++".xml"]}
+ ]},
+ Module = {module, [Name]},
+ ModuleSummary = {modulesummary, ShortDesc},
+ Description = {description, [?NL|FullDesc]},
+ Types = case Types1 of
+ [] -> [];
+ _ ->
+ [?NL, {section,[{title,["DATA TYPES"]},
+ {marker,[{id,"types"}],[]},
+ ?NL|types(Types1)]}]
+ end,
+ Funcs = functions(Functions),
+ See = seealso_module(Es),
+ Authors = {authors, authors(Es)},
+ {erlref,
+ [?NL,Header,
+ ?NL,Module,
+ ?NL,ModuleSummary,
+ ?NL,Description]
+ ++ Types ++
+ [?NL,Funcs,
+ ?NL,See,
+ ?NL,Authors]
+ }.
+
+layout_chapter(#xmlElement{name=overview, content=Es}) ->
+ Title = get_text(title, Es),
+ Header = {header, [
+ ?NL,{title,[Title]},
+ ?NL,{prepared,[""]},
+ ?NL,{docno,[""]},
+ ?NL,{date,[""]},
+ ?NL,{rev,[""]},
+ ?NL,{file, ["chapter.xml"]}
+ ]},
+ DescEs = get_content(description, Es),
+ FullDescEs = get_content(fullDescription, DescEs),
+ Sections = chapter_ify(FullDescEs, first),
+ {chapter, [?NL, Header, ?NL | Sections]}.
+
+chapter_ify([], _) ->
+ [];
+chapter_ify(Es, first) ->
+ %% Everything up to the first section should be made into
+ %% plain paragraphs -- or if no first section is found, everything
+ %% should be made into one
+ case find_next(h3, Es) of
+ {Es, []} ->
+ SubSections = subchapter_ify(Es, first),
+ [{section, [?NL,{title,["Overview"]},
+ ?NL | SubSections]}];
+ {FirstEs, RestEs} ->
+ otp_xmlify(FirstEs) ++ chapter_ify(RestEs, next)
+ end;
+chapter_ify([#xmlElement{name=h3} = E | Es], next) ->
+ {SectionEs, RestEs} = find_next(h3, Es),
+ SubSections = subchapter_ify(SectionEs, first),
+ {Marker, Title} = chapter_title(E),
+ [{section, [?NL,{marker,[{id,Marker}],[]},
+ ?NL,{title,[Title]},
+ ?NL | SubSections]} | chapter_ify(RestEs, next)].
+
+subchapter_ify([], _) ->
+ [];
+subchapter_ify(Es, first) ->
+ %% Everything up to the (possible) first subsection should be
+ %% made into plain paragraphs
+ {FirstEs, RestEs} = find_next(h4, Es),
+ otp_xmlify(FirstEs) ++ subchapter_ify(RestEs, next);
+subchapter_ify([#xmlElement{name=h4} = E | Es], next) ->
+ {SectionEs, RestEs} = find_next(h4, Es),
+ Elements = otp_xmlify(SectionEs),
+ {Marker, Title} = chapter_title(E),
+ [{section, [?NL,{marker,[{id,Marker}],[]},
+ ?NL,{title,[Title]},
+ ?NL | Elements]} | subchapter_ify(RestEs, next)].
+
+chapter_title(#xmlElement{content=Es}) -> % name = h3 | h4
+ case Es of
+ [#xmlElement{name=a} = E] ->
+ {get_attrval(name, E), get_text(E)}
+ end.
+
+%%--XHTML->XML transformation-------------------------------------------
+
+%% otp_xmlify(Es1) -> Es2
+%% Es1 = Es2 = [#xmlElement{} | #xmlText{}]
+%% Fix things that are allowed in XHTML but not in chapter/erlref DTDs.
+%% 1) lists (<ul>, <ol>, <dl>) and code snippets (<pre>) can not occur
+%% within a <p>, such a <p> must be splitted into a sequence of <p>,
+%% <ul>, <ol>, <dl> and <pre>.
+%% 2) <a> must only have either a href attribute (corresponds to a
+%% <seealso> or <url> in the XML code) in which case its content
+%% must be plain text; or a name attribute (<marker>).
+%% 3a) <b> content must be plain text.
+%% b) <em> content must be plain text (or actually a <code> element
+%% as well, but a simplification is used here).
+%% c) <pre> content must be plain text (or could actually contain
+%% <input> as well, but a simplification is used here).
+%% 4) <code> content must be plain text, or the element must be split
+%% into a list of elements.
+%% 5a) <h1>, <h2> etc is not allowed - replaced by its content within
+%% a <b> tag.
+%% b) <center> is not allowed - replaced by its content.
+%% c) <font> -"-
+%% 6) <table> is not allowed in erlref, translated to text instead.
+%% Also a <table> in chapter without a border is translated to text.
+%% A <table> in chapter with a border must contain a <tcaption>.
+%% 7) <sup> is not allowed - is replaced with its text content
+%% within parenthesis.
+%% 8) <blockquote> contents may need to be made into paragraphs
+%% 9) <th> (table header) is not allowed - is replaced by
+%% <td><em>...</em></td>.
+otp_xmlify([]) ->
+ [];
+otp_xmlify(Es0) ->
+ Es = case is_paragraph(hd(Es0)) of
+
+ %% If the first element is a <p> xmlElement, then
+ %% the entire element list is ready to be otp_xmlified.
+ true ->
+ Es0;
+
+ %% If the first element is not a <p> xmlElement, then all
+ %% elements up to the first <p> (or end of list) must be
+ %% made into a paragraph (using the {p, Es} construction)
+ %% before the list is otp_xmlified.
+ false ->
+ case find_next(p, Es0, []) of
+ {[#xmlText{value=Str}] = First, Rest} ->
+ %% Special case: Making a paragraph out of a
+ %% blank line isn't a great idea.
+ case is_empty(Str) of
+ true ->
+ Rest;
+ false ->
+ [{p,First}|Rest]
+ end;
+ {First, Rest} ->
+ [{p,First}|Rest]
+ end
+ end,
+
+ %% Fix paragraph breaks not needed in XHTML but in XML
+ EsFixed = otp_xmlify_fix(Es),
+
+ otp_xmlify_es(EsFixed).
+
+%% EDoc does not always translate empty lines (with leading "%%")
+%% as paragraph break, this is the fix
+otp_xmlify_fix(Es) ->
+ otp_xmlify_fix(Es, []).
+otp_xmlify_fix([#xmlText{value="\n \n"++_} = E1, E2 | Es], Res) ->
+ %% This is how it looks when generating an erlref from a .erl file
+ case is_paragraph(E2) of
+ false ->
+ {P, After} = find_p_ending(Es, []),
+ otp_xmlify_fix(After, [{p, [E2|P]}, E1 | Res]);
+ true ->
+ otp_xmlify_fix([E2|Es], [E1|Res])
+ end;
+otp_xmlify_fix([#xmlText{value="\n\n"} = E1, E2 | Es], Res) ->
+ %% This is how it looks when generating a chapter from overview.edoc
+ case is_paragraph(E2) of
+ false ->
+ {P, After} = find_p_ending(Es, []),
+ otp_xmlify_fix(After, [{p, [E2|P]}, E1 | Res]);
+ true ->
+ otp_xmlify_fix([E2|Es], [E1|Res])
+ end;
+otp_xmlify_fix([E|Es], Res) ->
+ otp_xmlify_fix(Es, [E|Res]);
+otp_xmlify_fix([], Res) ->
+ lists:reverse(Res).
+
+otp_xmlify_es([E | Es]) ->
+ case is_paragraph(E) of
+ true ->
+ case otp_xmlify_psplit(E) of
+
+ %% paragraph contained inline tags and text only
+ nosplit ->
+ otp_xmlify_e(E) ++ otp_xmlify_es(Es);
+
+ %% paragraph contained dl, ul and/or pre and has been
+ %% splitted
+ SubEs ->
+ lists:flatmap(fun otp_xmlify_e/1, SubEs) ++
+ otp_xmlify_es(Es)
+ end;
+ false ->
+ otp_xmlify_e(E) ++ otp_xmlify_es(Es)
+ end;
+otp_xmlify_es([]) ->
+ [].
+
+%% otp_xmlify_psplit(P) -> nosplit | [E]
+%% Handles case 1) above.
+%% Uses the {p, Es} construct, thus replaces an p xmlElement with one
+%% or more {p, Es} tuples if splitting the paraghrap is necessary.
+otp_xmlify_psplit(P) ->
+ otp_xmlify_psplit(p_content(P), [], []).
+otp_xmlify_psplit([#xmlElement{name=Name}=E | Es], Content, Res) ->
+ if
+ Name==blockquote; Name==ul; Name==ol; Name==dl; Name==pre;
+ Name==table ->
+ case Content of
+ [] ->
+ otp_xmlify_psplit(Es, [], [E|Res]);
+ [#xmlText{value=Str}] ->
+ %% Special case: Making a paragraph out of a blank
+ %% line isn't a great idea. Instead, this can be
+ %% viewed as the case above, where there is no
+ %% content to make a paragraph out of
+ case is_empty(Str) of
+ true ->
+ otp_xmlify_psplit(Es, [], [E|Res]);
+ false ->
+ Pnew = {p, lists:reverse(Content)},
+ otp_xmlify_psplit(Es, [], [E,Pnew|Res])
+ end;
+ _ ->
+ Pnew = {p, lists:reverse(Content)},
+ otp_xmlify_psplit(Es, [], [E,Pnew|Res])
+ end;
+
+ true ->
+ otp_xmlify_psplit(Es, [E|Content], Res)
+ end;
+otp_xmlify_psplit([E | Es], Content, Res) ->
+ otp_xmlify_psplit(Es, [E|Content], Res);
+otp_xmlify_psplit([], _Content, []) ->
+ nosplit;
+otp_xmlify_psplit([], [], Res) ->
+ lists:reverse(Res);
+otp_xmlify_psplit([], [#xmlText{value="\n\n"}], Res) ->
+ lists:reverse(Res);
+otp_xmlify_psplit([], Content, Res) ->
+ Pnew = {p, lists:reverse(Content)},
+ lists:reverse([Pnew|Res]).
+
+%% otp_xmlify_e(E) -> [E]
+%% This is the function which does the actual transformation of
+%% single elements, normally by making sure the content corresponds
+%% to what is allowed by the OTP DTDs.
+%% Returns a list of elements as the xmlification in some cases
+%% returns no element or more than one element (although in most cases
+%% exactly one element).
+otp_xmlify_e(#xmlElement{name=a} = E) -> % 2) above
+ otp_xmlify_a(E);
+otp_xmlify_e(#xmlElement{name=Tag} = E) % 3a-c)
+ when Tag==b; Tag==em; Tag==pre ->
+ Content = text_only(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=code} = E) -> % 4)
+ case catch text_only(E#xmlElement.content) of
+ {'EXIT', _Error} ->
+ otp_xmlify_code(E);
+ Content ->
+ [E#xmlElement{content=Content}]
+ end;
+otp_xmlify_e(#xmlElement{name=Tag} = E) % 5a
+ when Tag==h1; Tag==h2; Tag==h3; Tag==h4; Tag==h5 ->
+ Content = text_only(E#xmlElement.content),
+ [E#xmlElement{name=b, content=Content}];
+otp_xmlify_e(#xmlElement{name=Tag} = E) % 5b-c)
+ when Tag==center;
+ Tag==font ->
+ otp_xmlify_e(E#xmlElement.content);
+otp_xmlify_e(#xmlElement{name=table} = E) -> % 6)
+ case parent(E) of
+ module ->
+ otp_xmlify_table(E#xmlElement.content);
+ overview ->
+ Content0 = otp_xmlify_e(E#xmlElement.content),
+ Summary = #xmlText{value=get_attrval(summary, E)},
+ TCaption = E#xmlElement{name=tcaption,
+ attributes=[],
+ content=[Summary]},
+ Content = Content0 ++ [TCaption],
+ [E#xmlElement{attributes=[], content=Content}]
+ end;
+otp_xmlify_e(#xmlElement{name=tbody} = E) ->
+ otp_xmlify_e(E#xmlElement.content);
+otp_xmlify_e(#xmlElement{name=sup} = E) -> % 7)
+ Text = get_text(E),
+ [#xmlText{parents = E#xmlElement.parents,
+ pos = E#xmlElement.pos,
+ language = E#xmlElement.language,
+ value = "(" ++ Text ++ ")"}];
+otp_xmlify_e(#xmlElement{name=blockquote} = E) -> % 8)
+ Content = otp_xmlify_blockquote(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=th} = E) -> % 9)
+ Content = otp_xmlify_e(E#xmlElement.content),
+ EmE = E#xmlElement{name=em, content=Content},
+ [E#xmlElement{name=td, content=[EmE]}];
+otp_xmlify_e(#xmlElement{name=p} = E) -> % recurse
+ Content = otp_xmlify_e(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e({p, Content1}) ->
+ Content2 = otp_xmlify_e(Content1),
+ [{p, Content2}];
+otp_xmlify_e(#xmlElement{name=ul} = E) ->
+ Content = otp_xmlify_e(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=li} = E) ->
+ %% Content may need to be made into <p>s etc.
+ Content = otp_xmlify(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=dl} = E) ->
+ Content0 = otp_xmlify_e(E#xmlElement.content),
+ Content = otp_xmlify_dl(Content0),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=dt} = E) ->
+ %% Special fix: Markers in <taglist> <tag>s are not allowed,
+ %% save it using 'put' and place the marker first in the <item>
+ %% instead
+ Content = case E#xmlElement.content of
+ [#xmlElement{name=a} = A] ->
+ put(dt_marker, otp_xmlify_e(A)),
+ otp_xmlify_e(A#xmlElement.content);
+ _ ->
+ otp_xmlify_e(E#xmlElement.content)
+ end,
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=dd} = E) ->
+ %% Content may need to be made into <p>s etc.
+ Content0 = otp_xmlify(E#xmlElement.content),
+ Content = case get(dt_marker) of
+ undefined -> Content0;
+ [Marker] ->
+ put(dt_marker, undefined),
+ [Marker#xmlElement{content=[]}|Content0]
+ end,
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=tr} = E) ->
+ Content = otp_xmlify_e(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e(#xmlElement{name=td} = E) ->
+ Content = otp_xmlify_e(E#xmlElement.content),
+ [E#xmlElement{content=Content}];
+otp_xmlify_e([E | Es]) ->
+ otp_xmlify_e(E) ++ otp_xmlify_e(Es);
+otp_xmlify_e([]) ->
+ [];
+otp_xmlify_e(E) ->
+ [E].
+
+%%--Tags with special handling------------------------------------------
+
+%% otp_xmlify_a(A1) -> [A2]
+%% Takes an <a> element and filters the attributes to decide wheather
+%% its a seealso/url or a marker.
+%% In the case of a seealso/url, the href part is checked, making
+%% sure a .xml/.html file extension is removed (as DocBuilder inserts
+%% .html extension when resolving cross references).
+%% Also, references to other applications //App has a href attribute
+%% value "OTPROOT/..." (due to app_default being set to "OTPROOT" in
+%% docb_gen.erl), in this case both href attribute and content must be
+%% formatted correctly according to DocBuilder requirements.
+otp_xmlify_a(A) ->
+ [Attr0] = filter_a_attrs(A#xmlElement.attributes),
+ case Attr0 of
+ #xmlAttribute{name=href, value=Href0} -> % seealso | url
+ Content0 = text_only(A#xmlElement.content),
+ {Href, Content} = otp_xmlify_a_href(Href0, Content0),
+ [A#xmlElement{attributes=[Attr0#xmlAttribute{value=Href}],
+ content=Content}];
+ #xmlAttribute{name=name} -> % marker
+ Content = otp_xmlify_e(A#xmlElement.content),
+ [A#xmlElement{attributes=[Attr0], content=Content}]
+ end.
+
+%% filter_a_attrs(Attrs) -> [Attr]
+%% Removes all attributes from a <a> element except the href or
+%% name attribute.
+filter_a_attrs([#xmlAttribute{name=href} = Attr | _Attrs]) ->
+ [Attr];
+filter_a_attrs([#xmlAttribute{name=name} = Attr | _Attrs]) ->
+ [Attr];
+filter_a_attrs([_Attr|Attrs]) ->
+ filter_a_attrs(Attrs);
+filter_a_attrs([]) ->
+ [].
+
+%% otp_xmlify_a_href(Href0, Es0) -> {Href1, Es1}
+%% Href = string()
+otp_xmlify_a_href("#"++_ = Marker, Es0) -> % <seealso marker="#what">
+ {Marker, Es0};
+otp_xmlify_a_href("http:"++_ = URL, Es0) -> % external URL
+ {URL, Es0};
+otp_xmlify_a_href("OTPROOT"++AppRef, Es0) -> % <.. marker="App:FileRef
+ [AppS, "doc", FileRef1] = split(AppRef, "/"),
+ FileRef = AppS++":"++otp_xmlify_a_fileref(FileRef1, AppS),
+ [#xmlText{value=Str0} = T] = Es0,
+ Str = case split(Str0, "/") of
+ %% //Application
+ [AppS2] ->
+ %% AppS2 can differ from AppS
+ %% Example: xmerl/XMerL
+ AppS2;
+ [_AppS,ModRef] ->
+ case split(ModRef, ":") of
+ %% //Application/Module
+ [Module] ->
+ Module++"(3)";
+ %% //Application/Module:Type()
+ [_Module,_Type] ->
+ ModRef
+ end;
+ %% //Application/Module:Function/Arity
+ [_AppS,ModFunc,Arity] ->
+ ModFunc++"/"++Arity
+ end,
+ {FileRef, [T#xmlText{value=Str}]};
+otp_xmlify_a_href("../"++File, Es0) ->
+ %% Special case: This kind of relative path is used on some
+ %% places within i.e. EDoc and refers to a file within the same
+ %% application tree.
+ %% Correct the path according to the OTP directory structure
+ {"../../"++File, Es0};
+otp_xmlify_a_href(FileRef1, Es0) -> % File within the same application
+ FileRef2 = otp_xmlify_a_fileref(FileRef1, this),
+ {FileRef2, Es0}.
+
+%% otp_xmlify_a_fileref(FileRef1, AppS|this) -> FileRef2
+%% AppS = FileRef = string()
+otp_xmlify_a_fileref(FileRef1, AppS) ->
+ case split(FileRef1, ".#") of
+
+ %% EDoc default name is "overview-summary.html,
+ %% name of OTP User's Guide chapter is "chapter.xml"
+ ["overview-summary", _Ext] ->
+ "chapter";
+ ["overview-summary", _Ext, Marker] ->
+ "chapter#"++Marker;
+
+ [File, Ext] when Ext=="xml";
+ Ext=="html", AppS/=this ->
+ File;
+ [File, Ext, Marker0] ->
+ %% Here is an awkward solution to an awkward problem
+ %% The marker automatically inserted by DocBuilder at
+ %% each function does not seem to work for EDoc generated
+ %% ERLREFs.
+ %% So if the referenced marker is in an ERLREF generated
+ %% by EDoc, keep it "as is", ie "function-arity".
+ %% If the referenced marker is NOT in an ERLREF generated
+ %% by EDoc, the marker should be on the format
+ %% "function/arity".
+ %% The awkward part of the solution is to decide wheather
+ %% the ERLREF is generated by EDoc or not: Here we make
+ %% the decision based on which application the module
+ %% belongs to -- which is ok when the module was written
+ %% but probably not in the future...
+ EDocApps = ["edoc","hipe","syntax_tools","xmerl"],
+ IsEDocGenerated = lists:member(AppS, EDocApps),
+ Marker = if
+ %% The marker is in a file in *this*
+ %% application (which documentation obviously
+ %% is generated by EDoc), or it is in a file
+ %% in an application which documentation
+ %% is assumed to be generated by EDoc
+ AppS==this; IsEDocGenerated ->
+ Marker0;
+
+ %% The marker is in a file in an application
+ %% which documentation is assumed NOT to be
+ %% generated by EDoc
+ true ->
+ case split(Marker0, "-") of
+ [Func,Arity] ->
+ Func++"/"++Arity;
+ _ ->
+ Marker0
+ end
+ end,
+ if
+ %% Ignore file extension in file reference if it either
+ %% is ".xml" or if it is ".html" but AppS/=this, that
+ %% is, we're resolving an OTPROOT file reference
+ Ext=="xml";
+ Ext=="html", AppS/=this ->
+ File++"#"++Marker;
+ true ->
+ File++"."++Ext++"#"++Marker
+ end;
+
+ %% References to other files than XML files are kept as-is
+ _ ->
+ FileRef1
+ end.
+
+%% otp_xmlify_blockquote(Es1) -> Es2
+%% Ensures that the content of a <blockquote> is divided into
+%% <p>s using the {p, Es} construct.
+otp_xmlify_blockquote([#xmlElement{name=p} = E|Es]) ->
+ [E | otp_xmlify_blockquote(Es)];
+otp_xmlify_blockquote([#xmlText{} = E|Es]) ->
+ {P, After} = find_p_ending(Es, []),
+ [{p, [E|P]} | otp_xmlify_blockquote(After)];
+otp_xmlify_blockquote([]) ->
+ [].
+
+%% otp_xmlify_code(E) -> Es
+%% Takes a <code> xmlElement and split it into a list of <code> and
+%% other xmlElements. Necessary when it contains more than a single
+%% xmlText element.
+%% Example:
+%% #xmlElement{name=code,
+%% content=[#xmlText{}, #xmlElement{name=br}, #xmlText{}]}
+%% =>
+%% [#xmlElement{name=code, content=[#xmlText{}]},
+%% #xmlElement{name=br},
+%% #xmlElement{name=code, content=[#xmlText{}]}]
+otp_xmlify_code(E) ->
+ otp_xmlify_code(E, E#xmlElement.content, []).
+otp_xmlify_code(Code, [#xmlText{} = E|Es], Acc) ->
+ otp_xmlify_code(Code, Es, [Code#xmlElement{content=[E]}|Acc]);
+otp_xmlify_code(Code, [#xmlElement{} = E|Es], Acc) ->
+ otp_xmlify_code(Code, Es, [E|Acc]);
+otp_xmlify_code(_Code, [], Acc) ->
+ lists:reverse(Acc).
+
+%% otp_xmlify_dl(Es1) -> Es2
+%% Insert empty <dd> elements if necessary.
+%% OTP DTDs does not allow <taglist>s with <tag>s but no <item>s.
+otp_xmlify_dl([#xmlElement{name=dt} = E|Es]) ->
+ [E|otp_xmlify_dl(Es, E)];
+otp_xmlify_dl([E|Es]) ->
+ [E|otp_xmlify_dl(Es)];
+otp_xmlify_dl([]) ->
+ [].
+
+otp_xmlify_dl([#xmlElement{name=dd} = E|Es], _DT) ->
+ [E|otp_xmlify_dl(Es)];
+otp_xmlify_dl([#xmlElement{name=dt} = E|Es], DT) ->
+ DD = DT#xmlElement{name=dd, attributes=[], content=[]},
+ [DD,E|otp_xmlify_dl(Es, E)];
+otp_xmlify_dl([E|Es], DT) ->
+ [E|otp_xmlify_dl(Es, DT)];
+otp_xmlify_dl([], DT) ->
+ DD = DT#xmlElement{name=dd, attributes=[], content=[]},
+ [DD].
+
+%% otp_xmlify_table(Es1) -> Es2
+%% Transform <table> contents into "text", that is, inline elements.
+otp_xmlify_table([#xmlText{} = E|Es]) ->
+ [E | otp_xmlify_table(Es)];
+otp_xmlify_table([#xmlElement{name=tbody} = E|Es]) ->
+ otp_xmlify_table(E#xmlElement.content)++otp_xmlify_table(Es);
+otp_xmlify_table([#xmlElement{name=tr, content=Content}|Es]) ->
+ %% Insert newlines between table rows
+ otp_xmlify_table(Content)++[{br,[]}]++otp_xmlify_table(Es);
+otp_xmlify_table([#xmlElement{name=th, content=Content}|Es]) ->
+ [{em, Content} | otp_xmlify_table(Es)];
+otp_xmlify_table([#xmlElement{name=td, content=Content}|Es]) ->
+ otp_xmlify_e(Content) ++ otp_xmlify_table(Es);
+otp_xmlify_table([]) ->
+ [].
+
+%%--Misc help functions used by otp_xmlify/1 et al---------------------
+
+%% find_next(Tag, Es) -> {Es1, Es2}
+%% Returns {Es1, Es2} where Es1 is the list of all elements up to (but
+%% not including) the first element with tag Tag in Es, and Es2
+%% is the remaining elements of Es.
+find_next(Tag, Es) ->
+ find_next(Tag, Es, []).
+find_next(Tag, [#xmlElement{name=Tag} = E | Es], AccEs) ->
+ {lists:reverse(AccEs), [E|Es]};
+find_next(Tag, [E|Es], AccEs) ->
+ find_next(Tag, Es, [E|AccEs]);
+find_next(_Tag, [], AccEs) ->
+ {lists:reverse(AccEs), []}.
+
+%% find_p_ending(Es, []) -> {Es1, Es2}
+%% Returns {Es1, Es2} where Es1 is the list of all elements up to (but
+%% not including) the first paragraph break in Es, and Es2 is
+%% the remaining elements of Es2.
+%% Paragraph break = <p> tag or empty line
+%% the next blank line, <p> or end-of-list as P, and the remaining
+%% elements of Es as After.
+find_p_ending([#xmlText{value="\n \n"++_} = E|Es], P) ->
+ {lists:reverse(P), [E|Es]};
+find_p_ending([#xmlElement{name=p} = E|Es], P) ->
+ {lists:reverse(P), [E|Es]};
+find_p_ending([E|Es], P) ->
+ find_p_ending(Es, [E|P]);
+find_p_ending([], P) ->
+ {lists:reverse(P), []}.
+
+%% is_paragraph(E | P) -> bool()
+%% P = {p, Es}
+is_paragraph(#xmlElement{name=p}) -> true;
+is_paragraph({p, _Es}) -> true;
+is_paragraph(_E) -> false.
+
+%% p_content(E | P) -> Es
+p_content(#xmlElement{content=Content}) -> Content;
+p_content({p, Content}) -> Content.
+
+%% is_empty(Str) -> bool()
+%% Str = string()
+%% Returns true if Str is empty in the sense that it contains nothing
+%% but spaces, tabs or newlines.
+is_empty("\n"++Str) ->
+ is_empty(Str);
+is_empty(" "++Str) ->
+ is_empty(Str);
+is_empty("\t"++Str) ->
+ is_empty(Str);
+is_empty("") ->
+ true;
+is_empty(_) ->
+ false.
+
+%% split(Str, Seps) -> [Str]
+split(Str, Seps) ->
+ split(Str, Seps, []).
+
+split([Ch|Str], Seps, Acc) ->
+ case lists:member(Ch, Seps) of
+ true -> split(Str, Seps, Acc);
+ false -> split(Str, Seps, Acc, [Ch])
+ end;
+split([], _Seps, Acc) ->
+ lists:reverse(Acc).
+
+split([Ch|Str], Seps, Acc, Chs) ->
+ case lists:member(Ch, Seps) of
+ true -> split(Str, Seps, [lists:reverse(Chs)|Acc]);
+ false -> split(Str, Seps, Acc, [Ch|Chs])
+ end;
+split([], _Seps, Acc, Chs) ->
+ lists:reverse([lists:reverse(Chs)|Acc]).
+
+%%--Functions for creating an erlref document---------------------------
+
+%% function_name(F) -> string()
+%% F = #xmlElement{name=function}
+%% Returns the name of a function as "name/arity".
+function_name(E) ->
+ get_attrval(name, E) ++ "/" ++ get_attrval(arity, E).
+
+%% functions(Fs) -> Es
+%% Fs = [{Name, F}]
+%% Name = string() "name/arity"
+%% F = #xmlElement{name=function}
+functions(Fs) ->
+ Es = lists:flatmap(fun ({Name, E}) -> function(Name, E) end, Fs),
+ if
+ Es==[] ->
+ [];
+ true ->
+ {funcs, Es}
+ end.
+
+function(_Name, E=#xmlElement{content = Es}) ->
+ TypeSpec = get_content(typespec, Es),
+ [?NL,{func, [ ?NL,
+ {name,
+ case funcheader(TypeSpec) of
+ [] ->
+ signature(get_content(args, Es),
+ get_attrval(name, E));
+ Spec -> Spec
+ end
+ },
+ ?NL,{fsummary, fsummary(Es)},
+ ?NL,local_types(TypeSpec),
+ ?NL,{desc,
+ label_anchor(E)++
+ deprecated(Es)++
+ fulldesc(Es)++
+ seealso_function(Es)}
+ ]}].
+
+fsummary([]) -> ["\s"];
+fsummary(Es) ->
+ Desc = get_content(description, Es),
+ case get_content(briefDescription, Desc) of
+ [] ->
+ fsummary_equiv(Es); % no description at all if no equiv
+ ShortDesc ->
+ text_only(ShortDesc)
+ end.
+
+fsummary_equiv(Es) ->
+ case get_content(equiv, Es) of
+ [] -> ["\s"];
+ Es1 ->
+ case get_content(expr, Es1) of
+ [] -> ["\s"];
+ [Expr] ->
+ ["Equivalent to ", Expr, ".",?NL]
+ end
+ end.
+
+label_anchor(E) ->
+ case get_attrval(label, E) of
+ "" -> [];
+ Ref -> [{marker, [{id, Ref}],[]},?NL]
+ end.
+
+label_anchor(Content, E) ->
+ case get_attrval(label, E) of
+ "" -> Content;
+ Ref -> {p,[{marker, [{id, Ref}],[]},
+ {em, Content}]}
+ end.
+
+signature(Es, Name) ->
+ [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> term()", ?NL].
+
+arg(#xmlElement{content = Es}) ->
+ [get_text(argName, Es)].
+
+funcheader([]) -> [];
+funcheader(Es) ->
+ [t_name(get_elem(erlangName, Es))] ++ t_utype(get_elem(type, Es)).
+
+local_types([]) -> [];
+local_types(Es) ->
+ local_defs2(get_elem(localdef, Es)).
+
+local_defs2([]) -> [];
+local_defs2(Es) ->
+ {type,[?NL | [{v, localdef2(E)} || E <- Es]]}.
+
+%% Like localdef/1, but does not use label_anchor/2 -- we don't want any
+%% markers or em tags in <v> tag, plain text only!
+localdef2(#xmlElement{content = Es}) ->
+ case get_elem(typevar, Es) of
+ [] ->
+ t_utype(get_elem(type, Es));
+ [V] ->
+ t_var(V) ++ [" = "] ++ t_utype(get_elem(type, Es))
+ end.
+
+type_name(#xmlElement{content = Es}) ->
+ t_name(get_elem(erlangName, get_content(typedef, Es))).
+
+types(Ts) ->
+ Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E) end, Ts),
+ [?NL, {taglist,[?NL|Es]}].
+
+typedecl(Name, #xmlElement{content = Es}) ->
+ TypedefEs = get_content(typedef, Es),
+ Id = "type-"++Name,
+ [{tag, typedef(TypedefEs)},
+ ?NL,
+ {item, [{marker,[{id,Id}],[]} |
+ local_defs(get_elem(localdef, TypedefEs)) ++ fulldesc(Es)]},
+ ?NL].
+
+typedef(Es) ->
+ Name = ([t_name(get_elem(erlangName, Es)), "("]
+ ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
+ case get_elem(type, Es) of
+ [] ->
+ [{tt, Name}];
+ Type ->
+ [{tt, Name ++ [" = "] ++ t_utype(Type)}]
+ end.
+
+local_defs([]) -> [];
+local_defs(Es) ->
+ [?NL, {ul, [{li, [{tt, localdef(E)}]} || E <- Es]}].
+
+localdef(E = #xmlElement{content = Es}) ->
+ Var = case get_elem(typevar, Es) of
+ [] ->
+ [label_anchor(t_abstype(get_content(abstype, Es)), E)];
+ [V] ->
+ t_var(V)
+ end,
+ Var ++ [" = "] ++ t_utype(get_elem(type, Es)).
+
+deprecated(Es) ->
+ case get_content(deprecated, Es) of
+ [] -> [];
+ DeprEs ->
+ Es2 = get_content(fullDescription,
+ get_content(description, DeprEs)),
+ Es3 = otp_xmlify_e(Es2),
+ [{p, [{em, ["This function is deprecated: "]} |Es3]}, ?NL]
+ end.
+
+fulldesc(Es) ->
+ case get_content(fullDescription, get_content(description, Es)) of
+ [] ->
+ index_desc(Es);
+ Desc ->
+ [?NL|otp_xmlify(Desc)] ++ [?NL]
+ end.
+
+index_desc(Es) ->
+ Desc = get_content(description, Es),
+ case get_content(briefDescription, Desc) of
+ [] ->
+ equiv(Es); % no description at all if no equiv
+ ShortDesc ->
+ ShortDesc
+ end.
+
+seealso_module(Es) ->
+ case get_elem(see, Es) of
+ [] -> [];
+ Es1 ->
+ {section,[{title,["See also"]},{p,seq(fun see/1, Es1, [])}]}
+ end.
+seealso_function(Es) ->
+ case get_elem(see, Es) of
+ [] -> [];
+ Es1 ->
+ [{p, [{em, ["See also:"]}, " "] ++ seq(fun see/1, Es1, ["."])},
+ ?NL]
+ end.
+
+%% ELEMENT see PCDATA
+%% ATTLIST name PCDATA
+%% href PCDATA
+see(#xmlElement{content=Es0} = E) ->
+ Href0 = get_attrval(href, E),
+ {Href, Es} = otp_xmlify_a_href(Href0, Es0),
+ [{seealso, [{marker, Href}], Es}].
+
+equiv(Es) ->
+ case get_content(equiv, Es) of
+ [] -> ["\s"];
+ Es1 ->
+ case get_content(expr, Es1) of
+ [] -> [];
+ [Expr] ->
+ Expr1 = [Expr],
+ Expr2 = case get_elem(see, Es1) of
+ [] ->
+ {c,Expr1};
+ [E=#xmlElement{}] ->
+ case get_attrval(href, E) of
+ "" ->
+ {c,Expr1};
+ Ref ->
+ {seealso, [{marker, Ref}], Expr1}
+ end
+ end,
+ [{p, ["Equivalent to ", Expr2, "."]}, ?NL]
+ end
+ end.
+
+authors(Es) ->
+ case get_elem(author, Es) of
+ [] ->
+ [?NL,{aname,["\s"]},?NL,{email,["\s"]}];
+ Es1 ->
+ [?NL|seq(fun author/1, Es1, "", [])]
+ end.
+
+author(E=#xmlElement{}) ->
+ Name = case get_attrval(name, E) of
+ [] -> "\s";
+ N -> N
+ end,
+ Mail = case get_attrval(email, E) of
+ [] -> "\s";
+ M -> M
+ end,
+ [?NL,{aname,[Name]},?NL,{email,[Mail]}].
+
+t_name([E]) ->
+ N = get_attrval(name, E),
+ case get_attrval(module, E) of
+ "" -> N;
+ M ->
+ S = M ++ ":" ++ N,
+ case get_attrval(app, E) of
+ "" -> S;
+ A -> "//" ++ A ++ "/" ++ S
+ end
+ end.
+
+t_utype([E]) ->
+ t_utype_elem(E).
+
+t_utype_elem(E=#xmlElement{content = Es}) ->
+ case get_attrval(name, E) of
+ "" -> t_type(Es);
+ Name ->
+ T = t_type(Es),
+ case T of
+ [Name] -> T; % avoid generating "Foo::Foo"
+ T -> [Name] ++ ["::"] ++ T
+ end
+ end.
+
+t_type([E=#xmlElement{name = typevar}]) ->
+ t_var(E);
+t_type([E=#xmlElement{name = atom}]) ->
+ t_atom(E);
+t_type([E=#xmlElement{name = integer}]) ->
+ t_integer(E);
+t_type([E=#xmlElement{name = float}]) ->
+ t_float(E);
+t_type([#xmlElement{name = nil}]) ->
+ t_nil();
+t_type([#xmlElement{name = list, content = Es}]) ->
+ t_list(Es);
+t_type([#xmlElement{name = tuple, content = Es}]) ->
+ t_tuple(Es);
+t_type([#xmlElement{name = 'fun', content = Es}]) ->
+ t_fun(Es);
+t_type([#xmlElement{name = abstype, content = Es}]) ->
+ t_abstype(Es);
+t_type([#xmlElement{name = union, content = Es}]) ->
+ t_union(Es);
+t_type([#xmlElement{name = record, content = Es}]) ->
+ t_record(Es).
+
+t_var(E) ->
+ [get_attrval(name, E)].
+
+t_atom(E) ->
+ [get_attrval(value, E)].
+
+t_integer(E) ->
+ [get_attrval(value, E)].
+
+t_float(E) ->
+ [get_attrval(value, E)].
+
+t_nil() ->
+ ["[]"].
+
+t_list(Es) ->
+ ["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
+
+t_tuple(Es) ->
+ ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
+
+t_fun(Es) ->
+ ["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es),
+ [") -> "] ++ t_utype(get_elem(type, Es))).
+
+t_record([E|Es]) ->
+ ["#", get_attrval(value, E), "{"++ seq(fun t_field/1, Es) ++"}"].
+t_field(#xmlElement{name=field, content=[Atom,Type]}) ->
+ [get_attrval(value, Atom), "="] ++ t_utype_elem(Type).
+
+t_abstype(Es) ->
+ case split_at_colon(t_name(get_elem(erlangName, Es)),[]) of
+ {Mod,Type} ->
+ [Type, "("] ++
+ seq(fun t_utype_elem/1, get_elem(type, Es), [")"]) ++
+ [" (see module ", Mod, ")"];
+ Type ->
+ [Type, "("] ++
+ seq(fun t_utype_elem/1, get_elem(type, Es), [")"])
+ end.
+
+%% Split at one colon, but not at two (or more)
+split_at_colon([$:,$:|_]=Rest,Acc) ->
+ lists:reverse(Acc)++Rest;
+split_at_colon([$:|Type],Acc) ->
+ {lists:reverse(Acc),Type};
+split_at_colon([Char|Rest],Acc) ->
+ split_at_colon(Rest,[Char|Acc]);
+split_at_colon([],Acc) ->
+ lists:reverse(Acc).
+
+t_union(Es) ->
+ seq(fun t_utype_elem/1, Es, " | ", []).
+
+%% seq(Fun, Es)
+%% seq(Fun, Es, Tail)
+%% seq(Fun, Es, Sep, Tail) -> [string()]
+%% Fun = function(E) -> [string()]
+%% Sep = string()
+%% Tail = [string()]
+%% Applies Fun to each element E in Es and return the appended list of
+%% strings, separated by Sep which defaults to ", " and ended by Tail
+%% which defaults to [].
+seq(Fun, Es) ->
+ seq(Fun, Es, []).
+seq(Fun, Es, Tail) ->
+ seq(Fun, Es, ", ", Tail).
+seq(Fun, [E], _Sep, Tail) ->
+ Fun(E) ++ Tail;
+seq(Fun, [E | Es], Sep, Tail) ->
+ Fun(E) ++ [Sep] ++ seq(Fun, Es, Sep, Tail);
+seq(_Fun, [], _Sep, Tail) ->
+ Tail.
+
+%%--Misc functions for accessing fields etc-----------------------------
+
+%% Type definitions used below:
+%% E = #xmlElement{} | #xmlText{}
+%% Es = [E]
+%% Tag = atom(), XHTML tag
+%% Name = atom(), XHTML attribute name
+%% Attrs = [#xmlAttribute{}]
+%% Ts = [#xmlText{}]
+
+%% parent(E) -> module | overview
+parent(E) ->
+ Parents = E#xmlElement.parents,
+ {Parent,_} = lists:last(Parents),
+ Parent.
+
+%% get_elem(Tag, Es1) -> Es2
+%% Returns a list of all elements in Es which have the name Tag.
+get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
+ [E | get_elem(Name, Es)];
+get_elem(Name, [_ | Es]) ->
+ get_elem(Name, Es);
+get_elem(_, []) ->
+ [].
+
+%% get_attr(Name, Attrs1) -> Attrs2
+%% Returns a list of all attributes in Attrs1 which have the name Name.
+get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
+ [A | get_attr(Name, As)];
+get_attr(Name, [_ | As]) ->
+ get_attr(Name, As);
+get_attr(_, []) ->
+ [].
+
+%% get_attrval(Name, E) -> string()
+%% If E has one attribute with name Name, return its value, otherwise ""
+get_attrval(Name, #xmlElement{attributes = As}) ->
+ case get_attr(Name, As) of
+ [#xmlAttribute{value = V}] ->
+ V;
+ [] -> ""
+ end.
+
+%% get_content(Tag, Es1) -> Es2
+%% If there is one element in Es1 with name Tag, returns its contents,
+%% otherwise []
+get_content(Name, Es) ->
+ case get_elem(Name, Es) of
+ [#xmlElement{content = Es1}] ->
+ Es1;
+ [] -> []
+ end.
+
+%% get_text(Tag, Es) -> string()
+%% If there is one element in Es with name Tag, and its content is
+%% a single xmlText, return the value of this xmlText.
+%% Otherwise return "".
+get_text(Name, Es) ->
+ case get_content(Name, Es) of
+ [#xmlText{value = Text}] ->
+ Text;
+ [] -> ""
+ end.
+
+%% get_text(E) -> string()
+%% Return the value of an single xmlText which is the content of E,
+%% possibly recursively.
+get_text(#xmlElement{content=[#xmlText{value=Text}]}) ->
+ Text;
+get_text(#xmlElement{content=[E]}) ->
+ get_text(E).
+
+%% text_only(Es) -> Ts
+%% Takes a list of xmlElement and xmlText and return a lists of xmlText.
+text_only([#xmlElement{content = Content}|Es]) ->
+ text_only(Content) ++ text_only(Es);
+text_only([#xmlText{} = E |Es]) ->
+ [E | text_only(Es)];
+text_only([]) ->
+ [].