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