1%% Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% Copyright (c) 2001-2016 Richard Carlsson. Parts written by Ericsson
14%% are Copyright (c) Ericsson AB 2001-2020. All Rights Reserved.
15%%
16
17-module(docgen_edoc_xml_cb).
18
19%% This is the EDoc callback module for creating erlref
20%% documents (man pages) in XML format, and also a chapter
21%% document based on "overview.edoc".
22%%
23%% edoc:file(File, [{layout,docgen_edoc_xml_cb},{file_suffix,".xml"},
24%%                  {preprocess,true}]).
25%%
26%% The origin of this file is the edoc module otpsgml_layout.erl
27%% written by Richard Carlsson and Kenneth Lundin.
28
29-export([module/2, overview/2, makesee/1]).
30
31-include("xmerl.hrl").
32
33-define(NL, "\n").
34
35%%-User interface-------------------------------------------------------
36
37%% ERLREF
38module(Element, Opts) ->
39    SortP = proplists:get_value(sort_functions, Opts, true),
40    XML = layout_module(Element, SortP),
41    RootAttributes = root_attributes(Element, Opts),
42    xmerl:export_simple([XML], docgen_xmerl_xml_cb, RootAttributes).
43
44%% CHAPTER
45overview(Element, Opts) ->
46    XML = layout_chapter(Element),
47    RootAttributes = root_attributes(Element, Opts),
48    xmerl:export_simple([XML], docgen_xmerl_xml_cb, RootAttributes).
49
50%%--Internal functions--------------------------------------------------
51
52layout_module(#xmlElement{name = module, content = Es}=E, SortP) ->
53    put(type, module),
54    Name = get_attrval(name, E),
55    Desc = get_content(description, Es),
56    ShortDesc = text_only(get_content(briefDescription, Desc)),
57    FullDesc =  otp_xmlify(get_content(fullDescription, Desc)),
58    Types0 = get_content(typedecls, Es),
59    Types1 = lists:sort([{type_name(Et), Et} || Et <- Types0]),
60    Functions =
61	case SortP of
62	    true ->
63		lists:sort([{function_name(Ef), Ef} ||
64			       Ef <- get_content(functions, Es)]);
65	    false ->
66		[{function_name(Ef), Ef} ||
67		    Ef <- get_content(functions, Es)]
68	end,
69    Header = {header, [
70		       ?NL,{title, [Name]},
71		       ?NL,{prepared, [""]},
72		       ?NL,{responsible, [""]},
73		       ?NL,{docno, ["1"]},
74		       ?NL,{approved, [""]},
75		       ?NL,{checked, [""]},
76		       ?NL,{date, [""]},
77		       ?NL,{rev, ["A"]},
78		       ?NL,{file, [Name++".xml"]}
79		      ]},
80    Module = {module, [Name]},
81    ModuleSummary = {modulesummary, ShortDesc},
82    Description = {description, [?NL|FullDesc]},
83    Types = case Types1 of
84		[] -> [];
85		_ ->
86		    [?NL, {section,[{title,["DATA TYPES"]},
87				    {marker,[{id,"types"}],[]},
88				    ?NL|types(Types1)]}]
89	    end,
90    Funcs = functions(Functions),
91    See = seealso_module(Es),
92    Authors = {authors, authors(Es)},
93    {erlref,
94     [?NL,Header,
95      ?NL,Module,
96      ?NL,ModuleSummary,
97      ?NL,Description]
98     ++ Types ++
99     [?NL,Funcs,
100      ?NL,See,
101      ?NL,Authors]
102    }.
103
104root_attributes(Element, Opts) ->
105    Encoding = case get_attrval(encoding, Element) of
106                   "" ->
107                       DefaultEncoding = epp:default_encoding(),
108                       proplists:get_value(encoding, Opts, DefaultEncoding);
109                   Enc ->
110                       Enc
111               end,
112    [#xmlAttribute{name=encoding, value=reformat_encoding(Encoding)}].
113
114%% epp:default_encoding/0 returns 'utf8'
115reformat_encoding(utf8) -> "UTF-8";
116reformat_encoding(List) when is_list(List) ->
117    case string:lowercase(List) of
118        "utf8" -> "UTF-8";
119        _ -> List
120    end;
121reformat_encoding(Other) -> Other.
122
123layout_chapter(#xmlElement{name=overview, content=Es}) ->
124    put(type, chapter),
125    Title = get_text(title, Es),
126    Header = {header, [
127		       ?NL,{title,[Title]},
128		       ?NL,{prepared,[""]},
129		       ?NL,{docno,[""]},
130		       ?NL,{date,[""]},
131		       ?NL,{rev,[""]},
132		       ?NL,{file, ["chapter.xml"]}
133		      ]},
134    DescEs = get_content(description, Es),
135    FullDescEs = get_content(fullDescription, DescEs),
136    Sections = chapter_ify(FullDescEs, first),
137    {chapter, [?NL, Header, ?NL | Sections]}.
138
139chapter_ify([], _) ->
140    [];
141chapter_ify(Es, first) ->
142    %% Everything up to the first section should be made into
143    %% plain paragraphs -- or if no first section is found, everything
144    %% should be made into one
145    case find_next(h3, Es) of
146	{Es, []} ->
147	    SubSections = subchapter_ify(Es, first),
148	    [{section, [?NL,{title,["Overview"]},
149			?NL | SubSections]}];
150	{FirstEs, RestEs} ->
151	    otp_xmlify(FirstEs) ++ chapter_ify(RestEs, next)
152    end;
153chapter_ify([#xmlElement{name=h3} = E | Es], next) ->
154    {SectionEs, RestEs} = find_next(h3, Es),
155    SubSections = subchapter_ify(SectionEs, first),
156    {Marker, Title} = chapter_title(E),
157    [{section, [?NL,{marker,[{id,Marker}],[]},
158		?NL,{title,[Title]},
159		?NL | SubSections]} | chapter_ify(RestEs, next)].
160
161subchapter_ify([], _) ->
162    [];
163subchapter_ify(Es, first) ->
164    %% Everything up to the (possible) first subsection should be
165    %% made into plain paragraphs
166    {FirstEs, RestEs} = find_next(h4, Es),
167    otp_xmlify(FirstEs) ++ subchapter_ify(RestEs, next);
168subchapter_ify([#xmlElement{name=h4} = E | Es], next) ->
169    {SectionEs, RestEs} = find_next(h4, Es),
170    Elements = otp_xmlify(SectionEs),
171    {Marker, Title} = chapter_title(E),
172    [{section, [?NL,{marker,[{id,Marker}],[]},
173		?NL,{title,[Title]},
174		?NL | Elements]} | subchapter_ify(RestEs, next)].
175
176chapter_title(#xmlElement{content=Es}) -> % name = h3 | h4
177    case Es of
178	[#xmlElement{name=a} = E] ->
179	    {get_attrval(name, E), get_text(E)}
180    end.
181
182%%--XHTML->XML transformation-------------------------------------------
183
184%% otp_xmlify(Es1) -> Es2
185%%   Es1 = Es2 = [#xmlElement{} | #xmlText{}]
186%% Fix things that are allowed in XHTML but not in chapter/erlref DTDs.
187%% 1)  lists (<ul>, <ol>, <dl>) and code snippets (<pre>) cannot occur
188%%     within a <p>, such a <p> must be splitted into a sequence of <p>,
189%%     <ul>, <ol>, <dl> and <pre>.
190%% 2)  <a> must only have either a href attribute (corresponds to a
191%%     <seealso> or <url> in the XML code) in which case its content
192%%     must be plain text; or a name attribute (<marker>).
193%% 3a) <b> content must be plain text.
194%%  b) <em> content must be plain text (or actually a <code> element
195%%     as well, but a simplification is used here).
196%%  c) <pre> content must be plain text (or could actually contain
197%%     <input> as well, but a simplification is used here).
198%% 4)  <code> content must be plain text, or the element must be split
199%%     into a list of elements.
200%% 5a) <h1>, <h2> etc is not allowed - replaced by its content within
201%%     a <b> tag.
202%%  b) <center> is not allowed - replaced by its content.
203%%  c) <font>   -"-
204%% 6)  <table> is not allowed in erlref, translated to text instead.
205%%     Also a <table> in chapter without a border is translated to text.
206%%     A <table> in chapter with a border must contain a <tcaption>.
207%% 7)  <sup> is not allowed - is replaced with its text content
208%%     within parenthesis.
209%% 8)  <blockquote> contents may need to be made into paragraphs
210%% 9)  <th> (table header) is not allowed - is replaced by
211%%     <td><em>...</em></td>.
212%% 10) <img src=""> is not allowed, replace with <image file="">
213otp_xmlify([]) ->
214    [];
215otp_xmlify(Es0) ->
216    Es = case is_paragraph(hd(Es0)) of
217
218	     %% If the first element is a <p> xmlElement, then
219	     %% the entire element list is ready to be otp_xmlified.
220	     true ->
221		 Es0;
222
223	     %% If the first element is not a <p> xmlElement, then all
224	     %% elements up to the first <p> (or end of list) must be
225	     %% made into a paragraph (using the {p, Es} construction)
226	     %% before the list is otp_xmlified.
227	     false ->
228		 case find_next(p, Es0, []) of
229		     {[#xmlText{value=Str}] = First, Rest} ->
230			 %% Special case: Making a paragraph out of a
231			 %% blank line isn't a great idea.
232			 case is_empty(Str) of
233			     true ->
234				 Rest;
235			     false ->
236				 [{p,First}|Rest]
237			 end;
238		     {First, Rest} ->
239			 [{p,First}|Rest]
240		 end
241	 end,
242
243    %% Fix paragraph breaks not needed in XHTML but in XML
244    EsFixed = otp_xmlify_fix(Es),
245
246    otp_xmlify_es(EsFixed).
247
248%% EDoc does not always translate empty lines (with leading "%%")
249%% as paragraph break, this is the fix
250otp_xmlify_fix(Es) ->
251    otp_xmlify_fix(Es, []).
252otp_xmlify_fix([#xmlText{value="\n \n"++_} = E1, E2 | Es], Res) ->
253    %% This is how it looks when generating an erlref from a .erl file
254    case is_paragraph(E2) of
255	false ->
256	    {P, After} = find_p_ending(Es, []),
257	    otp_xmlify_fix(After, [{p, [E2|P]}, E1 | Res]);
258	true ->
259	    otp_xmlify_fix([E2|Es], [E1|Res])
260    end;
261otp_xmlify_fix([#xmlText{value="\n\n"} = E1, E2 | Es], Res) ->
262    %% This is how it looks when generating a chapter from overview.edoc
263    case is_paragraph(E2) of
264	false ->
265	    {P, After} = find_p_ending(Es, []),
266	    otp_xmlify_fix(After, [{p, [E2|P]}, E1 | Res]);
267	true ->
268	    otp_xmlify_fix([E2|Es], [E1|Res])
269    end;
270otp_xmlify_fix([E|Es], Res) ->
271    otp_xmlify_fix(Es, [E|Res]);
272otp_xmlify_fix([], Res) ->
273    lists:reverse(Res).
274
275otp_xmlify_es([E | Es]) ->
276    case is_paragraph(E) of
277	true ->
278	    case otp_xmlify_psplit(E) of
279
280		%% paragraph contained inline tags and text only
281		nosplit ->
282		    otp_xmlify_e(E) ++ otp_xmlify_es(Es);
283
284		%% paragraph contained dl, ul and/or pre and has been
285		%% splitted
286		SubEs ->
287		    lists:flatmap(fun otp_xmlify_e/1, SubEs) ++
288			otp_xmlify_es(Es)
289	    end;
290	false ->
291	    otp_xmlify_e(E) ++ otp_xmlify_es(Es)
292    end;
293otp_xmlify_es([]) ->
294    [].
295
296%% otp_xmlify_psplit(P) -> nosplit | [E]
297%% Handles case 1) above.
298%% Uses the {p, Es} construct, thus replaces an p xmlElement with one
299%% or more {p, Es} tuples if splitting the paraghrap is necessary.
300otp_xmlify_psplit(P) ->
301    otp_xmlify_psplit(p_content(P), [], []).
302otp_xmlify_psplit([#xmlElement{name=Name}=E | Es], Content, Res) ->
303    if
304	Name==blockquote; Name==ul; Name==ol; Name==dl; Name==pre;
305	Name==table ->
306	    case Content of
307		[] ->
308		    otp_xmlify_psplit(Es, [], [E|Res]);
309		[#xmlText{value=Str}] ->
310		    %% Special case: Making a paragraph out of a blank
311		    %% line isn't a great idea. Instead, this can be
312		    %% viewed as the case above, where there is no
313		    %% content to make a paragraph out of
314		    case is_empty(Str) of
315			true ->
316			    otp_xmlify_psplit(Es, [], [E|Res]);
317			false ->
318			    Pnew = {p, lists:reverse(Content)},
319			    otp_xmlify_psplit(Es, [], [E,Pnew|Res])
320		    end;
321		_ ->
322		    Pnew = {p, lists:reverse(Content)},
323		    otp_xmlify_psplit(Es, [], [E,Pnew|Res])
324	    end;
325
326	true ->
327	    otp_xmlify_psplit(Es, [E|Content], Res)
328    end;
329otp_xmlify_psplit([E | Es], Content, Res) ->
330    otp_xmlify_psplit(Es, [E|Content], Res);
331otp_xmlify_psplit([], _Content, []) ->
332    nosplit;
333otp_xmlify_psplit([], [], Res) ->
334    lists:reverse(Res);
335otp_xmlify_psplit([], [#xmlText{value="\n\n"}], Res) ->
336    lists:reverse(Res);
337otp_xmlify_psplit([], Content, Res) ->
338    Pnew = {p, lists:reverse(Content)},
339    lists:reverse([Pnew|Res]).
340
341%% otp_xmlify_e(E) -> [E]
342%% This is the function which does the actual transformation of
343%% single elements, normally by making sure the content corresponds
344%% to what is allowed by the OTP DTDs.
345%% Returns a list of elements as the xmlification in some cases
346%% returns no element or more than one element (although in most cases
347%% exactly one element).
348otp_xmlify_e(#xmlElement{name=a} = E) ->       % 2) above
349    otp_xmlify_a(E);
350otp_xmlify_e(#xmlElement{name=Tag} = E)        % 3a-c)
351  when Tag==b; Tag==em; Tag==pre ->
352    Content = text_only(E#xmlElement.content),
353    [E#xmlElement{content=Content}];
354otp_xmlify_e(#xmlElement{name=code} = E) ->    % 4)
355    case catch text_only(E#xmlElement.content) of
356	{'EXIT', _Error} ->
357	    otp_xmlify_code(E);
358	Content ->
359	    [E#xmlElement{content=Content}]
360    end;
361otp_xmlify_e(#xmlElement{name=Tag} = E)        % 5a
362  when Tag==h1; Tag==h2; Tag==h3; Tag==h4; Tag==h5 ->
363     {Name, Text} = text_and_a_name_only(E#xmlElement.content),
364     [Name, E#xmlElement{name=b, content=Text}];
365otp_xmlify_e(#xmlElement{name=Tag} = E)        % 5b-c)
366  when Tag==center;
367       Tag==font ->
368    otp_xmlify_e(E#xmlElement.content);
369otp_xmlify_e(#xmlElement{name=table} = E) ->   % 6)
370    case parent(E) of
371	module ->
372	    otp_xmlify_table(E#xmlElement.content);
373	overview ->
374	    Content0 = otp_xmlify_e(E#xmlElement.content),
375	    Summary = #xmlText{value=get_attrval(summary, E)},
376	    TCaption = E#xmlElement{name=tcaption,
377				    attributes=[],
378				    content=[Summary]},
379	    Content = Content0 ++ [TCaption],
380	    [E#xmlElement{attributes=[], content=Content}]
381    end;
382otp_xmlify_e(#xmlElement{name=tbody} = E) ->
383    otp_xmlify_e(E#xmlElement.content);
384otp_xmlify_e(#xmlElement{name=sup} = E) ->     % 7)
385    Text = get_text(E),
386    [#xmlText{parents = E#xmlElement.parents,
387	      pos = E#xmlElement.pos,
388	      language = E#xmlElement.language,
389	      value = "(" ++ Text ++ ")"}];
390otp_xmlify_e(#xmlElement{name=blockquote} = E) -> % 8)
391    Content = otp_xmlify_blockquote(E#xmlElement.content),
392    [E#xmlElement{content=Content}];
393otp_xmlify_e(#xmlElement{name=th} = E) ->      % 9)
394    Content = otp_xmlify_e(E#xmlElement.content),
395    EmE = E#xmlElement{name=em, content=Content},
396    [E#xmlElement{name=td, content=[EmE]}];
397otp_xmlify_e(#xmlElement{name=p} = E) ->       % recurse
398    Content = otp_xmlify_e(E#xmlElement.content),
399    [E#xmlElement{content=Content}];
400otp_xmlify_e({p, Content1}) ->
401    Content2 = otp_xmlify_e(Content1),
402    [{p, Content2}];
403otp_xmlify_e(#xmlElement{name=ul} = E) ->
404    Content = otp_xmlify_e(E#xmlElement.content),
405    [E#xmlElement{content=Content}];
406otp_xmlify_e(#xmlElement{name=li} = E) ->
407    %% Content may need to be made into <p>s etc.
408    Content = otp_xmlify(E#xmlElement.content),
409    [E#xmlElement{content=Content}];
410otp_xmlify_e(#xmlElement{name=dl} = E) ->
411    Content0 = otp_xmlify_e(E#xmlElement.content),
412    Content = otp_xmlify_dl(Content0),
413    [E#xmlElement{content=Content}];
414otp_xmlify_e(#xmlElement{name=dt} = E) ->
415    %% Special fix: Markers in <taglist> <tag>s are not allowed,
416    %% save it using 'put' and place the marker first in the <item>
417    %% instead
418    Content = case E#xmlElement.content of
419		  [#xmlElement{name=a} = A] ->
420		      put(dt_marker, otp_xmlify_e(A)),
421		      otp_xmlify_e(A#xmlElement.content);
422		  _ ->
423		      otp_xmlify_e(E#xmlElement.content)
424	      end,
425    [E#xmlElement{content=Content}];
426otp_xmlify_e(#xmlElement{name=dd} = E) ->
427    %% Content may need to be made into <p>s etc.
428    Content0 = otp_xmlify(E#xmlElement.content),
429    Content = case get(dt_marker) of
430		  undefined -> Content0;
431		  [Marker] ->
432		      put(dt_marker, undefined),
433		      [Marker#xmlElement{content=[]}|Content0]
434	      end,
435    [E#xmlElement{content=Content}];
436otp_xmlify_e(#xmlElement{name=tr} = E) ->
437    Content = otp_xmlify_e(E#xmlElement.content),
438    [E#xmlElement{content=Content}];
439otp_xmlify_e(#xmlElement{name=td} = E) ->
440    Content = otp_xmlify_e(E#xmlElement.content),
441    [E#xmlElement{content=Content}];
442otp_xmlify_e(#xmlElement{name=img} = E) ->     % 10)
443    Content = otp_xmlify_e(E#xmlElement.content),
444    [otp_xmlify_img(E#xmlElement{ content = Content })];
445otp_xmlify_e([E | Es]) ->
446    otp_xmlify_e(E) ++ otp_xmlify_e(Es);
447otp_xmlify_e([]) ->
448    [];
449otp_xmlify_e(E) ->
450    [E].
451
452%%--Tags with special handling------------------------------------------
453
454%% otp_xmlify_a(A1) -> [A2]
455%% Takes an <a> element and filters the attributes to decide wheather
456%% its a seealso/url or a marker.
457%% In the case of a seealso/url, the href part is checked, making
458%% sure a .xml/.html file extension is removed.
459%% Also, references to other applications //App has a href attribute
460%% value "OTPROOT/..." (due to app_default being set to "OTPROOT")
461%% , in this case both href attribute and content must be
462%% formatted correctly according to requirements.
463otp_xmlify_a(A) ->
464    [Attr0] = filter_a_attrs(A#xmlElement.attributes),
465    case Attr0 of
466	#xmlAttribute{name=href, value=Href0} -> % seealso | url
467	    Content0 = text_only(A#xmlElement.content),
468	    {Href, Content} = otp_xmlify_a_href(Href0, Content0),
469	    [A#xmlElement{attributes=[Attr0#xmlAttribute{value=Href}],
470			  content=Content}];
471	#xmlAttribute{name=name} -> % marker
472	    Content = otp_xmlify_e(A#xmlElement.content),
473	    [A#xmlElement{attributes=[Attr0], content=Content}]
474    end.
475
476%% filter_a_attrs(Attrs) -> [Attr]
477%% Removes all attributes from a <a> element except the href or
478%% name attribute.
479filter_a_attrs([#xmlAttribute{name=href} = Attr | _Attrs]) ->
480    [Attr];
481filter_a_attrs([#xmlAttribute{name=name} = Attr | _Attrs]) ->
482    [Attr];
483filter_a_attrs([_Attr|Attrs]) ->
484    filter_a_attrs(Attrs);
485filter_a_attrs([]) ->
486    [].
487
488%% otp_xmlify_a_href(Href0, Es0) -> {Href1, Es1}
489%%   Href = string()
490otp_xmlify_a_href("#"++_ = Marker, Es0) -> % <see* marker="#what">
491    {make_mfa_anchor(Marker), Es0};
492otp_xmlify_a_href("http:"++_ = URL, Es0) -> % external URL
493    {URL, Es0};
494otp_xmlify_a_href("https:"++_ = URL, Es0) -> % external URL
495    {URL, Es0};
496otp_xmlify_a_href("OTPROOT"++AppRef, Es0) -> % <.. marker="App:FileRef
497    [AppS, "doc", FileRef1] = split(AppRef, "/"),
498    FileRef = AppS++":"++otp_xmlify_a_fileref(FileRef1, AppS),
499    [#xmlText{value=Str0} = T] = Es0,
500    Str = case split(Str0, "/") of
501	      %% //Application
502	      [AppS2] ->
503		  %% AppS2 can differ from AppS
504		  %% Example: xmerl/XMerL
505		  AppS2;
506	      [_AppS,ModRef] ->
507		  case split(ModRef, ":") of
508		      %% //Application/Module
509		      [Module] ->
510			  Module++"(3)";
511		      %% //Application/Module:Type()
512		      [_Module,_Type] ->
513			  ModRef
514		  end;
515	      %% //Application/Module:Function/Arity
516	      [_AppS,ModFunc,Arity] ->
517		  ModFunc++"/"++Arity
518	  end,
519    {FileRef, [T#xmlText{value=Str}]};
520otp_xmlify_a_href("../"++File, Es0) ->
521    %% Special case: This kind of relative path is used on some
522    %% places within i.e. EDoc and refers to a file within the same
523    %% application tree.
524    %% Correct the path according to the OTP directory structure
525    {"../../"++File, Es0};
526otp_xmlify_a_href(FileRef1, Es0) -> % File within the same application
527    FileRef2 = otp_xmlify_a_fileref(FileRef1, this),
528    {FileRef2, Es0}.
529
530%% otp_xmlify_a_fileref(FileRef1, AppS|this) -> FileRef2
531%%   AppS = FileRef = string()
532otp_xmlify_a_fileref(FileRef1, AppS) ->
533    case split(FileRef1, ".#") of
534
535	%% EDoc default name is "overview-summary.html,
536	%% name of OTP User's Guide chapter is "chapter.xml"
537	["overview-summary", _Ext] ->
538	    "chapter";
539	["overview-summary", _Ext, Marker] ->
540	    "chapter#"++Marker;
541
542	[File, Ext] when Ext=="xml";
543			 Ext=="html", AppS/=this ->
544	    File;
545	[File, Ext, Marker0] ->
546	    Marker = make_mfa_anchor(Marker0),
547	    if
548		%% Ignore file extension in file reference if it either
549		%% is ".xml" or if it is ".html" but AppS/=this, that
550		%% is, we're resolving an OTPROOT file reference
551		Ext=="xml";
552		Ext=="html", AppS/=this ->
553		    File++"#"++Marker;
554		true ->
555		    File++"."++Ext++"#"++Marker
556	    end;
557
558	%% References to other files than XML files are kept as-is
559	_ ->
560	    FileRef1
561    end.
562
563%% otp_xmlify_blockquote(Es1) -> Es2
564%% Ensures that the content of a <blockquote> is divided into
565%% <p>s using the {p, Es} construct.
566otp_xmlify_blockquote([#xmlElement{name=p} = E|Es]) ->
567    [E | otp_xmlify_blockquote(Es)];
568otp_xmlify_blockquote([#xmlText{} = E|Es]) ->
569    {P, After} = find_p_ending(Es, []),
570    [{p, [E|P]} | otp_xmlify_blockquote(After)];
571otp_xmlify_blockquote([]) ->
572    [].
573
574%% otp_xmlify_code(E) -> Es
575%% Takes a <code> xmlElement and split it into a list of <code> and
576%% other xmlElements. Necessary when it contains more than a single
577%% xmlText element.
578%% Example:
579%% #xmlElement{name=code,
580%%             content=[#xmlText{}, #xmlElement{name=br}, #xmlText{}]}
581%% =>
582%% [#xmlElement{name=code, content=[#xmlText{}]},
583%%  #xmlElement{name=br},
584%%  #xmlElement{name=code, content=[#xmlText{}]}]
585otp_xmlify_code(E) ->
586    otp_xmlify_code(E, E#xmlElement.content, []).
587otp_xmlify_code(Code, [#xmlText{} = E|Es], Acc) ->
588    otp_xmlify_code(Code, Es, [Code#xmlElement{content=[E]}|Acc]);
589otp_xmlify_code(Code, [#xmlElement{} = E|Es], Acc) ->
590    otp_xmlify_code(Code, Es, [E|Acc]);
591otp_xmlify_code(_Code, [], Acc) ->
592    lists:reverse(Acc).
593
594%% otp_xmlify_dl(Es1) -> Es2
595%% Insert empty <dd> elements if necessary.
596%% OTP DTDs does not allow <taglist>s with <tag>s but no <item>s.
597otp_xmlify_dl([#xmlElement{name=dt} = E|Es]) ->
598    [E|otp_xmlify_dl(Es, E)];
599otp_xmlify_dl([E|Es]) ->
600    [E|otp_xmlify_dl(Es)];
601otp_xmlify_dl([]) ->
602    [].
603
604otp_xmlify_dl([#xmlElement{name=dd} = E|Es], _DT) ->
605    [E|otp_xmlify_dl(Es)];
606otp_xmlify_dl([#xmlElement{name=dt} = E|Es], DT) ->
607    DD = DT#xmlElement{name=dd, attributes=[], content=[]},
608    [DD,E|otp_xmlify_dl(Es, E)];
609otp_xmlify_dl([E|Es], DT) ->
610    [E|otp_xmlify_dl(Es, DT)];
611otp_xmlify_dl([], DT) ->
612    DD = DT#xmlElement{name=dd, attributes=[], content=[]},
613    [DD].
614
615%% otp_xmlify_table(Es1) -> Es2
616%% Transform <table> contents into "text", that is, inline elements.
617otp_xmlify_table([#xmlText{} = E|Es]) ->
618    [E | otp_xmlify_table(Es)];
619otp_xmlify_table([#xmlElement{name=tbody} = E|Es]) ->
620    otp_xmlify_table(E#xmlElement.content)++otp_xmlify_table(Es);
621otp_xmlify_table([#xmlElement{name=tr, content=Content}|Es]) ->
622    %% Insert newlines between table rows
623    otp_xmlify_table(Content)++[{br,[]}]++otp_xmlify_table(Es);
624otp_xmlify_table([#xmlElement{name=th, content=Content}|Es]) ->
625    [{em, Content} | otp_xmlify_table(Es)];
626otp_xmlify_table([#xmlElement{name=td, content=Content}|Es]) ->
627    otp_xmlify_e(Content) ++ otp_xmlify_table(Es);
628otp_xmlify_table([]) ->
629    [].
630
631%% otp_xmlify_img(E) -> Es.
632%% Transforms a <img src=""> into <image file="">
633otp_xmlify_img(E0) ->
634    Attrs = lists:map(
635	      fun(#xmlAttribute{ name = src, value = Path} = A) ->
636		      V = otp_xmlify_a_fileref(Path,this),
637		      A#xmlAttribute{ name = file,
638				      value = V };
639		 (A) ->
640		      A
641	      end,E0#xmlElement.attributes),
642    E0#xmlElement{name = image, expanded_name = image,
643		       attributes = Attrs}.
644
645%%--Misc help functions used by otp_xmlify/1 et al---------------------
646
647%% make_mfa_anchor(Marker) -> NewMarker
648%%  Returns the anchor in func/arity format if the heuristic guesses that
649%%  the anchor points to a func/arity
650make_mfa_anchor(Marker) ->
651    case split(Marker,"-") of
652        [Func,Arity] ->
653            try list_to_integer(Arity) of
654                _ ->
655                    Func ++ "/" ++ Arity
656            catch _:_ ->
657                    Marker
658            end;
659        _ ->
660            Marker
661    end.
662
663%% find_next(Tag, Es) -> {Es1, Es2}
664%% Returns {Es1, Es2} where Es1 is the list of all elements up to (but
665%% not including) the first element with tag Tag in Es, and Es2
666%% is the remaining elements of Es.
667find_next(Tag, Es) ->
668    find_next(Tag, Es, []).
669find_next(Tag, [#xmlElement{name=Tag} = E | Es], AccEs) ->
670    {lists:reverse(AccEs), [E|Es]};
671find_next(Tag, [E|Es], AccEs) ->
672    find_next(Tag, Es, [E|AccEs]);
673find_next(_Tag, [], AccEs) ->
674    {lists:reverse(AccEs), []}.
675
676%% find_p_ending(Es, []) -> {Es1, Es2}
677%% Returns {Es1, Es2} where Es1 is the list of all elements up to (but
678%% not including) the first paragraph break in Es, and Es2 is
679%% the remaining elements of Es2.
680%% Paragraph break = <p> tag or empty line
681%% the next blank line, <p> or end-of-list as P, and the remaining
682%% elements of Es as After.
683find_p_ending([#xmlText{value="\n \n"++_} = E|Es], P) ->
684    {lists:reverse(P), [E|Es]};
685find_p_ending([#xmlElement{name=p} = E|Es], P) ->
686    {lists:reverse(P), [E|Es]};
687find_p_ending([E|Es], P) ->
688    find_p_ending(Es, [E|P]);
689find_p_ending([], P) ->
690    {lists:reverse(P), []}.
691
692%% is_paragraph(E | P) -> bool()
693%%   P = {p, Es}
694is_paragraph(#xmlElement{name=p}) -> true;
695is_paragraph({p, _Es}) -> true;
696is_paragraph(_E) -> false.
697
698%% p_content(E | P) -> Es
699p_content(#xmlElement{content=Content}) -> Content;
700p_content({p, Content}) -> Content.
701
702%% is_empty(Str) -> bool()
703%%   Str = string()
704%% Returns true if Str is empty in the sense that it contains nothing
705%% but spaces, tabs or newlines.
706is_empty("\n"++Str) ->
707    is_empty(Str);
708is_empty(" "++Str) ->
709    is_empty(Str);
710is_empty("\t"++Str) ->
711    is_empty(Str);
712is_empty("") ->
713    true;
714is_empty(_) ->
715    false.
716
717%% split(Str, Seps) -> [Str]
718split(Str, Seps) ->
719    split(Str, Seps, []).
720
721split([Ch|Str], Seps, Acc) ->
722    case lists:member(Ch, Seps) of
723	true ->  split(Str, Seps, Acc);
724	false -> split(Str, Seps, Acc, [Ch])
725    end;
726split([], _Seps, Acc) ->
727    lists:reverse(Acc).
728
729split([Ch|Str], Seps, Acc, Chs) ->
730    case lists:member(Ch, Seps) of
731	true ->  split(Str, Seps, [lists:reverse(Chs)|Acc]);
732	false -> split(Str, Seps, Acc, [Ch|Chs])
733    end;
734split([], _Seps, Acc, Chs) ->
735    lists:reverse([lists:reverse(Chs)|Acc]).
736
737%%--Functions for creating an erlref document---------------------------
738
739%% function_name(F) -> string()
740%%   F = #xmlElement{name=function}
741%% Returns the name of a function as "name/arity".
742function_name(E) ->
743    get_attrval(name, E) ++ "/" ++ get_attrval(arity, E).
744
745%% functions(Fs) -> Es
746%%   Fs = [{Name, F}]
747%%     Name = string()  "name/arity"
748%%     F = #xmlElement{name=function}
749functions(Fs) ->
750    Es = lists:flatmap(fun ({Name, E}) -> function(Name, E) end, Fs),
751    if
752	Es==[] ->
753	    [];
754	true ->
755	    {funcs, Es}
756    end.
757
758function(_Name, E=#xmlElement{content = Es}) ->
759    TypeSpec = get_content(typespec, Es),
760    FuncHeaders =
761        case funcheader(TypeSpec) of
762            [] ->
763                [signature(get_content(args, Es), get_attrval(name, E))];
764            Specs ->
765                Specs
766        end,
767    [?NL, {func, [?NL]++
768		 [{name, [{since,""}], Spec} || Spec <- FuncHeaders]++
769		 [?NL, {fsummary, fsummary(Es)},
770		  ?NL, local_types(TypeSpec),
771		  ?NL, {desc, label_anchor(E)++
772		              deprecated(Es)++
773		              fulldesc(Es)++
774		              seealso_function(Es)}]}].
775
776fsummary([]) -> ["\s"];
777fsummary(Es) ->
778    Desc = get_content(description, Es),
779    case get_content(briefDescription, Desc) of
780	[] ->
781	    fsummary_equiv(Es);    % no description at all if no equiv
782	ShortDesc ->
783	    text_only(ShortDesc)
784    end.
785
786fsummary_equiv(Es) ->
787    case get_content(equiv, Es) of
788	[] -> ["\s"];
789	Es1 ->
790	    case get_content(expr, Es1) of
791		[] -> ["\s"];
792		[Expr] ->
793		    ["Equivalent to ", Expr, ".",?NL]
794	    end
795    end.
796
797label_anchor(E) ->
798    case get_attrval(label, E) of
799	"" -> [];
800	Ref -> [{marker, [{id, Ref}],[]},?NL]
801    end.
802
803label_anchor(Content, E) ->
804    case get_attrval(label, E) of
805	"" -> Content;
806	Ref -> {p,[{marker, [{id, Ref}],[]},
807		   {em, Content}]}
808    end.
809
810signature(Es, Name) ->
811    [Name, "("] ++ seq(fun arg/1, Es) ++ [") -> term()", ?NL].
812
813arg(#xmlElement{content = Es}) ->
814    [get_text(argName, Es)].
815
816funcheader([]) -> [];
817funcheader(Es) ->
818    Name = t_name(get_elem(erlangName, Es)),
819    [ [Name] ++ t_utype([E]) || E <- get_elem(type, Es)].
820
821
822local_types([]) -> [];
823local_types(Es) ->
824    local_defs2(get_elem(localdef, Es)).
825
826-define(LOCAL_TYPES, edoc_local_defs).
827
828local_defs2([]) -> [];
829local_defs2(Es) ->
830    case collect_local_types(Es) of
831        [] -> local_defs3(Es);
832        LocalTypes ->
833            ?LOCAL_TYPES = ets:new(?LOCAL_TYPES, [named_table]),
834            true = ets:insert(?LOCAL_TYPES, LocalTypes),
835            try
836                local_defs3(Es)
837            after
838                ets:delete(?LOCAL_TYPES)
839            end
840    end.
841
842local_defs3(Es) ->
843    {type,[?NL | [{v, localdef2(E)} || E <- Es]]}.
844
845%% Does not work well for parametrized types.
846collect_local_types(Es) ->
847    lists:append([collect_local_type(E) || E <- Es]).
848
849collect_local_type(#xmlElement{content = Es}) ->
850    case get_elem(typevar, Es) of
851        [] ->
852            [{t_abstype(get_content(abstype, Es))}];
853        [_] ->
854            []
855    end.
856
857%% Like localdef/1, but does not use label_anchor/2 -- we don't want any
858%% markers or em tags in <v> tag, plain text only!
859%% When used stand-alone, EDoc generates links to local types. An ETS
860%% table holds local types, to avoid generating links to them.
861localdef2(#xmlElement{content = Es}) ->
862    Var = case get_elem(typevar, Es) of
863              [] ->
864		  [t_abstype(get_content(abstype, Es))];
865              [V] ->
866                  t_var(V)
867          end,
868    Var ++ [" = "] ++ t_utype(get_elem(type, Es)).
869
870type_name(#xmlElement{content = Es}) ->
871    t_name(get_elem(erlangName, get_content(typedef, Es))).
872
873types(Ts) ->
874    Es = lists:flatmap(fun ({Name, E}) -> typedecl(Name, E) end, Ts),
875    [?NL, {taglist,[?NL|Es]}].
876
877typedecl(Name, #xmlElement{content = Es}) ->
878    TypedefEs = get_content(typedef, Es),
879    Id = "type-"++Name,
880    [{tag, [{marker,[{id,Id}],[]}] ++ typedef(TypedefEs)},
881     ?NL,
882     {item, local_defs(get_elem(localdef, TypedefEs)) ++ fulldesc(Es)},
883     ?NL].
884
885typedef(Es) ->
886    Name = ([t_name(get_elem(erlangName, Es)), "("]
887  	    ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])),
888    case get_elem(type, Es) of
889 	 [] ->
890	    Name;
891 	 Type ->
892	    Name ++ [" = "] ++ t_utype(Type)
893    end.
894
895local_defs([]) -> [{p,[]}];
896local_defs(Es) ->
897    [?NL, {ul, [{li, [{p, localdef(E)}]} || E <- Es]}].
898
899localdef(E = #xmlElement{content = Es}) ->
900    Var = case get_elem(typevar, Es) of
901	      [] ->
902		  [label_anchor(t_abstype(get_content(abstype, Es)), E)];
903	      [V] ->
904		  t_var(V)
905	  end,
906    Var ++ [" = "] ++ t_utype(get_elem(type, Es)).
907
908deprecated(Es) ->
909    case get_content(deprecated, Es) of
910	[] -> [];
911	DeprEs ->
912	    Es2 = get_content(fullDescription,
913			      get_content(description, DeprEs)),
914	    Es3 = otp_xmlify_e(Es2),
915	    [{p, [{em, ["This function is deprecated: "]} |Es3]}, ?NL]
916    end.
917
918fulldesc(Es) ->
919    case get_content(fullDescription, get_content(description, Es)) of
920	[] ->
921	    index_desc(Es);
922	Desc ->
923	    [?NL|otp_xmlify(Desc)] ++ [?NL]
924    end.
925
926index_desc(Es) ->
927    Desc = get_content(description, Es),
928    case get_content(briefDescription, Desc) of
929	[] ->
930	    equiv(Es);    % no description at all if no equiv
931	ShortDesc ->
932	    ShortDesc
933    end.
934
935seealso_module(Es) ->
936    case get_elem(see, Es) of
937	[] -> [];
938	Es1 ->
939	    {section,[{title,["See also"]},{p,seq(fun see/1, Es1, [])}]}
940    end.
941
942seealso_function(Es) ->
943    case get_elem(see, Es) of
944	[] -> [];
945	Es1 ->
946	    [{p, [{em, ["See also:"]}, " "] ++ seq(fun see/1, Es1, ["."])},
947	     ?NL]
948    end.
949
950%% ELEMENT see PCDATA
951%% ATTLIST name PCDATA
952%%         href PCDATA
953see(#xmlElement{content=Es0} = E) ->
954    Href0 = get_attrval(href, E),
955    {Href, Es} = otp_xmlify_a_href(Href0, Es0),
956    [makesee(Href, Es)].
957
958equiv(Es) ->
959    case get_content(equiv, Es) of
960	[] -> ["\s"];
961	Es1 ->
962	    case get_content(expr, Es1) of
963		[] -> [];
964		[Expr] ->
965		    Expr1 = [Expr],
966		    Expr2 = case get_elem(see, Es1) of
967				[] ->
968				    {c,Expr1};
969				[E=#xmlElement{}] ->
970 				    case get_attrval(href, E) of
971 					"" ->
972 					    {c,Expr1};
973 					Ref0 ->
974                                            {Ref, _Es2} = otp_xmlify_a_href(Ref0, [E]),
975                                            makesee(Ref, Expr1)
976 				    end
977			    end,
978		    [{p, ["Equivalent to ", Expr2, "."]}, ?NL]
979	    end
980    end.
981
982makesee(Ref, Es) ->
983    {Tag, Marker} = makesee(Ref),
984    {Tag, [{marker,Marker}], Es}.
985makesee(Ref) ->
986    case string:split(Ref,"#") of
987        ["chapter"] ->
988            {seeguide,"chapter"};
989        ["chapter",Anchor] ->
990            {seeguide,"chapter#" ++ Anchor};
991        [Mod,"type-"++Anchor] ->
992            {seeerl,Mod ++ "#type-" ++ Anchor};
993        ["",_Anchor] ->
994            case get(type) of
995                chapter ->
996                    {seeguide, Ref};
997                module ->
998                    case split(Ref,"/") of
999                        [_,_] ->
1000                            {seemfa, Ref};
1001                        _ ->
1002                            {seeerl, Ref}
1003                    end
1004            end;
1005        _Else ->
1006            case split(Ref,":") of
1007                [_,"index"] ->
1008                    {seeapp, Ref};
1009                _ ->
1010                    case split(Ref,"/") of
1011                        [_,_] ->
1012                            {seemfa, Ref};
1013                        _ ->
1014                            {seeerl, Ref}
1015                    end
1016            end
1017    end.
1018
1019authors(Es) ->
1020    case get_elem(author, Es) of
1021	[] ->
1022	    [?NL,{aname,["\s"]},?NL,{email,["\s"]}];
1023	Es1 ->
1024	    [?NL|seq(fun author/1, Es1, "", [])]
1025    end.
1026
1027author(E=#xmlElement{}) ->
1028    Name = case get_attrval(name, E) of
1029	       [] -> "\s";
1030	       N -> N
1031	   end,
1032    Mail = case get_attrval(email, E) of
1033	       [] -> "\s";
1034	       M -> M
1035	   end,
1036    [?NL,{aname,[Name]},?NL,{email,[Mail]}].
1037
1038t_name([E | _]) ->
1039    N = get_attrval(name, E),
1040    case get_attrval(module, E) of
1041	"" -> N;
1042	M ->
1043	    S = M ++ ":" ++ N,
1044	    case get_attrval(app, E) of
1045		"" -> S;
1046		A -> "//" ++ A ++ "/" ++ S
1047	    end
1048    end.
1049
1050t_utype([E]) ->
1051    flatten_type(t_utype_elem(E)).
1052
1053%% Make sure see also are top elements of lists.
1054flatten_type(T) ->
1055    [case is_integer(E) of
1056         true -> [E];
1057         false -> E
1058     end || E <- lists:flatten(T)].
1059
1060t_utype_elem(E=#xmlElement{content = Es}) ->
1061    case get_attrval(name, E) of
1062	"" -> t_type(Es);
1063	Name ->
1064	    T = t_type(Es),
1065	    case T of
1066		[Name] -> T;    % avoid generating "Foo::Foo"
1067		T -> [Name] ++ ["::"] ++ T
1068	    end
1069    end.
1070
1071t_type([E=#xmlElement{name = typevar}]) ->
1072    t_var(E);
1073t_type([E=#xmlElement{name = atom}]) ->
1074    t_atom(E);
1075t_type([E=#xmlElement{name = integer}]) ->
1076    t_integer(E);
1077t_type([E=#xmlElement{name = range}]) ->
1078    t_range(E);
1079t_type([E=#xmlElement{name = float}]) ->
1080    t_float(E);
1081t_type([#xmlElement{name = nil}]) ->
1082    t_nil();
1083t_type([#xmlElement{name = list, content = Es}]) ->
1084    t_list(Es);
1085t_type([#xmlElement{name = nonempty_list, content = Es}]) ->
1086    t_nonempty_list(Es);
1087t_type([#xmlElement{name = tuple, content = Es}]) ->
1088    t_tuple(Es);
1089t_type([#xmlElement{name = 'fun', content = Es}]) ->
1090    t_fun(Es);
1091t_type([E = #xmlElement{name = abstype, content = Es}]) ->
1092    t_abstype(E, Es);
1093t_type([#xmlElement{name = union, content = Es}]) ->
1094    t_union(Es);
1095t_type([#xmlElement{name = record, content = Es}]) ->
1096    t_record(Es);
1097t_type([#xmlElement{name = map, content = Es}]) ->
1098    t_map(Es).
1099
1100t_var(E) ->
1101    [get_attrval(name, E)].
1102
1103t_atom(E) ->
1104    [get_attrval(value, E)].
1105
1106t_integer(E) ->
1107    [get_attrval(value, E)].
1108
1109t_range(E) ->
1110    [get_attrval(value, E)].
1111
1112t_float(E) ->
1113    [get_attrval(value, E)].
1114
1115t_nil() ->
1116    ["[]"].
1117
1118t_list(Es) ->
1119    ["["] ++ t_utype(get_elem(type, Es)) ++ ["]"].
1120
1121t_nonempty_list(Es) ->
1122    ["["] ++ t_utype(get_elem(type, Es)) ++ [", ...]"].
1123
1124t_tuple(Es) ->
1125    ["{"] ++ seq(fun t_utype_elem/1, Es, ["}"]).
1126
1127t_fun(Es) ->
1128    ["("] ++ seq(fun t_utype_elem/1, get_content(argtypes, Es),
1129		 [") -> "] ++ t_utype(get_elem(type, Es))).
1130
1131t_record([E|Es]) ->
1132    ["#", get_attrval(value, E), "{"++ seq(fun t_field/1, Es) ++"}"].
1133
1134t_field(#xmlElement{name=field, content=[Atom,Type]}) ->
1135    [get_attrval(value, Atom), "="] ++ t_utype_elem(Type).
1136
1137t_map(Es) ->
1138    ["#{"] ++ seq(fun t_map_field/1, Es, ["}"]).
1139
1140t_map_field(E = #xmlElement{name = map_field, content = [K,V]}) ->
1141    KElem = t_utype_elem(K),
1142    VElem = t_utype_elem(V),
1143    AS = case get_attrval(assoc_type, E) of
1144             "assoc" -> " => ";
1145             "exact" -> " := "
1146         end,
1147    [KElem ++ AS ++ VElem].
1148
1149t_abstype(E, Es) ->
1150    see_type(E, t_abstype(Es)).
1151
1152t_abstype(Es) ->
1153    Name = t_name(get_elem(erlangName, Es)),
1154    [Name, "("] ++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"]).
1155
1156see_type(E, Es0) ->
1157    case get_attrval(href, E) of
1158        [] -> Es0;
1159        Href0 ->
1160            try
1161                false = is_local_type(Es0),
1162                %% Fails for parametrized types:
1163                Text = #xmlText{value = lists:append(Es0)},
1164                {Href, Es} = otp_xmlify_a_href(Href0, [Text]),
1165                [makesee(Href, Es)]
1166            catch
1167                _:_ ->
1168                    Es0
1169            end
1170    end.
1171
1172is_local_type(Es) ->
1173    try
1174        [_] = ets:lookup(?LOCAL_TYPES, Es),
1175        true
1176    catch
1177        _:_->
1178            false
1179    end.
1180
1181t_union(Es) ->
1182    seq(fun t_utype_elem/1, Es, " | ", []).
1183
1184%% seq(Fun, Es)
1185%% seq(Fun, Es, Tail)
1186%% seq(Fun, Es, Sep, Tail) -> [string()]
1187%%   Fun = function(E) -> [string()]
1188%%   Sep = string()
1189%%   Tail = [string()]
1190%% Applies Fun to each element E in Es and return the appended list of
1191%% strings, separated by Sep which defaults to ", " and ended by Tail
1192%% which defaults to [].
1193seq(Fun, Es) ->
1194    seq(Fun, Es, []).
1195seq(Fun, Es, Tail) ->
1196    seq(Fun, Es, ", ", Tail).
1197seq(Fun, [E], _Sep, Tail) ->
1198    Fun(E) ++ Tail;
1199seq(Fun, [E | Es], Sep, Tail) ->
1200    Fun(E) ++ [Sep] ++ seq(Fun, Es, Sep, Tail);
1201seq(_Fun, [], _Sep, Tail) ->
1202    Tail.
1203
1204%%--Misc functions for accessing fields etc-----------------------------
1205
1206%% Type definitions used below:
1207%%   E = #xmlElement{} | #xmlText{}
1208%%   Es = [E]
1209%%   Tag = atom(), XHTML tag
1210%%   Name = atom(), XHTML attribute name
1211%%   Attrs = [#xmlAttribute{}]
1212%%   Ts = [#xmlText{}]
1213
1214%% parent(E) -> module | overview
1215parent(E) ->
1216    Parents = E#xmlElement.parents,
1217    {Parent,_} = lists:last(Parents),
1218    Parent.
1219
1220%% get_elem(Tag, Es1) -> Es2
1221%% Returns a list of all elements in Es which have the name Tag.
1222get_elem(Name, [#xmlElement{name = Name} = E | Es]) ->
1223    [E | get_elem(Name, Es)];
1224get_elem(Name, [_ | Es]) ->
1225    get_elem(Name, Es);
1226get_elem(_, []) ->
1227    [].
1228
1229%% get_attr(Name, Attrs1) -> Attrs2
1230%% Returns a list of all attributes in Attrs1 which have the name Name.
1231get_attr(Name, [#xmlAttribute{name = Name} = A | As]) ->
1232    [A | get_attr(Name, As)];
1233get_attr(Name, [_ | As]) ->
1234    get_attr(Name, As);
1235get_attr(_, []) ->
1236    [].
1237
1238%% get_attrval(Name, E) -> string()
1239%% If E has one attribute with name Name, return its value, otherwise ""
1240get_attrval(Name, #xmlElement{attributes = As}) ->
1241    case get_attr(Name, As) of
1242	[#xmlAttribute{value = V}] ->
1243	    V;
1244	[] -> ""
1245    end.
1246
1247%% get_content(Tag, Es1) -> Es2
1248%% If there is one element in Es1 with name Tag, returns its contents,
1249%% if there are no tags, return [],
1250%% if there are multiple, merge their contents.
1251get_content(Name, Es) ->
1252    case get_elem(Name, Es) of
1253        [#xmlElement{content = Es1}] ->
1254            Es1;
1255        [] -> [];
1256        Elems ->
1257            lists:append([Es1 || #xmlElement{content = Es1} <- Elems])
1258    end.
1259
1260%% get_text(Tag, Es) -> string()
1261%% If there is one element in Es with name Tag, and its content is
1262%% a single xmlText, return the value of this xmlText.
1263%% Otherwise return "".
1264get_text(Name, Es) ->
1265    case get_content(Name, Es) of
1266	[#xmlText{value = Text}] ->
1267	    Text;
1268	[] -> ""
1269    end.
1270
1271%% get_text(E) -> string()
1272%% Return the value of an single xmlText which is the content of E,
1273%% possibly recursively.
1274get_text(#xmlElement{content=[#xmlText{value=Text}]}) ->
1275    Text;
1276get_text(#xmlElement{content=[E]}) ->
1277    get_text(E).
1278
1279%% text_and_name_only(Es) -> {N, Ts}
1280text_and_a_name_only(Es) ->
1281    case [Name || #xmlElement{
1282                     name = a,
1283                     attributes = [#xmlAttribute{name=name}]}=Name <- Es] of
1284        [Name|_] ->
1285            {Name#xmlElement{content = []}, text_only(Es)};
1286        [] ->
1287            {"", text_only(Es)}
1288    end.
1289
1290%% text_only(Es) -> Ts
1291%% Takes a list of xmlElement and xmlText and return a lists of xmlText.
1292text_only([#xmlElement{content = Content}|Es]) ->
1293    text_only(Content) ++ text_only(Es);
1294text_only([#xmlText{} = E |Es]) ->
1295    [E | text_only(Es)];
1296text_only([]) ->
1297    [].
1298