diff options
Diffstat (limited to 'lib/erl_docgen/src')
-rw-r--r-- | lib/erl_docgen/src/docgen_edoc_xml_cb.erl | 1154 | ||||
-rw-r--r-- | lib/erl_docgen/src/docgen_otp_specs.erl (renamed from lib/erl_docgen/src/otp_specs.erl) | 0 | ||||
-rw-r--r-- | lib/erl_docgen/src/docgen_xmerl_xml_cb.erl | 88 | ||||
-rw-r--r-- | lib/erl_docgen/src/docgen_xml_check.erl | 45 |
4 files changed, 1287 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([]) -> + []. diff --git a/lib/erl_docgen/src/otp_specs.erl b/lib/erl_docgen/src/docgen_otp_specs.erl index edb437a942..edb437a942 100644 --- a/lib/erl_docgen/src/otp_specs.erl +++ b/lib/erl_docgen/src/docgen_otp_specs.erl diff --git a/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl b/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl new file mode 100644 index 0000000000..089b8f0c7d --- /dev/null +++ b/lib/erl_docgen/src/docgen_xmerl_xml_cb.erl @@ -0,0 +1,88 @@ +%% ``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_xmerl_xml_cb). + +%% This is the callback module for exporting XHTML to a DocBuilder +%% erlref or chapter document in XML format. +%% See docb_edoc_xml_cb.erl for further information. +%% +%% The origin of this file is the xmerl module xmerl_otpsgml.erl +%% written by Ulf Wiger and Richard Carlsson. + +-export(['#xml-inheritance#'/0]). + +-export(['#root#'/4, + '#element#'/5, + '#text#'/1]). + +-include("xmerl.hrl"). + +'#xml-inheritance#'() -> + [xmerl_xml]. + +'#root#'(Data, _Attrs, [], _E) -> + ["<",DTD,">"] = hd(hd(Data)), + ["<?xml version=\"1.0\" encoding=\"latin1\" ?>\n", + "<!DOCTYPE "++DTD++" SYSTEM \""++DTD++".dtd\">\n", + Data]. + +'#element#'(Tag, Data, Attrs, _Parents, _E) -> + {NewTag, NewAttrs} = convert_tag(Tag, Attrs), + xmerl_lib:markup(NewTag, NewAttrs, Data). + +'#text#'(Text) -> + xmerl_lib:export_text(Text). + +%% Utility functions + +convert_tag(a, [Attr]) -> + case Attr#xmlAttribute.name of + href -> + Val = Attr#xmlAttribute.value, + case is_url(Val) of + true -> + {url, [Attr]}; + false -> + {seealso, [Attr#xmlAttribute{name=marker}]} + end; + name -> + {marker, [Attr#xmlAttribute{name=id}]} + end; +convert_tag(b, Attrs) -> {em, Attrs}; +convert_tag(blockquote, Attrs) -> {quote, Attrs}; +convert_tag(code, Attrs) -> {c, Attrs}; +convert_tag(dd, Attrs) -> {item, Attrs}; +convert_tag(dl, Attrs) -> {taglist, Attrs}; +convert_tag(dt, Attrs) -> {tag, Attrs}; +convert_tag(li, Attrs) -> {item, Attrs}; +convert_tag(ol, Attrs) -> {list, Attrs}; +convert_tag(strong, Attrs) -> {em, Attrs}; +convert_tag(td, Attrs) -> {cell, Attrs}; +convert_tag(tr, Attrs) -> {row, Attrs}; +convert_tag(tt, Attrs) -> {c, Attrs}; +convert_tag(ul, Attrs) -> {list, Attrs}; +convert_tag(underline, Attrs) -> {em, Attrs}; +convert_tag(Tag, Attrs) -> {Tag, Attrs}. + +is_url("http:"++_) -> true; +is_url("../"++_) -> true; +is_url(FileRef) -> + case filename:extension(FileRef) of + "" -> false; % no extension = xml file, DocBuilder resolves + _Ext -> true % extension, DocBuilder must not resolve + end. diff --git a/lib/erl_docgen/src/docgen_xml_check.erl b/lib/erl_docgen/src/docgen_xml_check.erl new file mode 100644 index 0000000000..5912e22e7b --- /dev/null +++ b/lib/erl_docgen/src/docgen_xml_check.erl @@ -0,0 +1,45 @@ +%% ``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 License for the specific language governing rights and limitations +%% under the License. +%% +%% The Initial Developer of the Original Code is Ericsson Utvecklings AB. +%% Portions created by Ericsson are Copyright 1999-2000, Ericsson +%% Utvecklings AB. All Rights Reserved.'' +%% +%% $Id$ +%% +-module(docb_xml_check). + +-export([validate/1]). +-deprecated([{validate,1,next_major_release}]). + +%% validate(File) -> ok | error | {error, badfile} +%% File = string(), file name with or without ".xml" extension +%% If XML validation fails for a file, the error information from +%% Xmerl is printed to terminal and the function returns error. +validate(File0) -> + File = case filename:extension(File0) of + ".xml" -> File0; + _ -> File0++".xml" + end, + case filelib:is_regular(File) of + true -> + DtdDir = docb_util:dtd_dir(), + case catch xmerl_scan:file(File, [{validation,true}, + {fetch_path,[DtdDir]}]) of + {'EXIT', Error} -> + io:format("~p~n", [Error]), + error; + {_Doc, _Misc} -> + ok + end; + false -> + {error, badfile} + end. |