View source with formatted comments or as raw
    1/*  Part of SWI-Prolog
    2
    3    Author:        Jan Wielemaker
    4    E-mail:        J.Wielemaker@vu.nl
    5    WWW:           http://www.swi-prolog.org
    6    Copyright (c)  2006-2022, University of Amsterdam
    7			      VU University Amsterdam
    8			      CWI, Amsterdam
    9                              SWI-Prolog Solutions b.v.
   10    All rights reserved.
   11
   12    Redistribution and use in source and binary forms, with or without
   13    modification, are permitted provided that the following conditions
   14    are met:
   15
   16    1. Redistributions of source code must retain the above copyright
   17       notice, this list of conditions and the following disclaimer.
   18
   19    2. Redistributions in binary form must reproduce the above copyright
   20       notice, this list of conditions and the following disclaimer in
   21       the documentation and/or other materials provided with the
   22       distribution.
   23
   24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   25    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
   26    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
   27    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
   28    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
   29    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
   30    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
   31    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   32    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   33    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
   34    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   35    POSSIBILITY OF SUCH DAMAGE.
   36*/
   37
   38:- module(pldoc_html,
   39	  [ doc_for_file/2,             % +FileSpec, +Options
   40	    doc_write_html/3,           % +Stream, +Title, +Term
   41	    doc_for_wiki_file/2,        % +FileSpec, +Options
   42					% Support doc_index
   43	    doc_page_dom/3,             % +Title, +Body, -DOM
   44	    print_html_head/1,          % +Stream
   45	    predref//1,                 % +PI //
   46	    predref//2,                 % +PI, Options //
   47	    nopredref//1,               % +PI //
   48	    module_info/3,              % +File, +Options0, -Options
   49	    doc_hide_private/3,         % +Doc0, -Doc, +Options
   50	    edit_button//2,             % +File, +Options, //
   51	    source_button//2,           % +File, +Options, //
   52	    zoom_button//2,             % +File, +Options, //
   53            pred_edit_button//2,        % +PredInd, +Options, //
   54	    object_edit_button//2,      % +Obj, +Options, //
   55	    object_source_button//2,    % +Obj, +Options, //
   56	    doc_resources//1,           % +Options
   57	    ensure_doc_objects/1,       % +File
   58					% Support other backends
   59	    doc_file_objects/5,         % +FSpec, -File, -Objs, -FileOpts, +Opts
   60	    existing_linked_file/2,     % +FileSpec, -Path
   61	    unquote_filespec/2,         % +FileSpec, -Unquoted
   62	    doc_tag_title/2,            % +Tag, -Title
   63	    mode_anchor_name/2,         % +Mode, -Anchor
   64	    pred_anchor_name/3,         % +Head, -PI, -Anchor
   65	    private/2,                  % +Obj, +Options
   66	    (multifile)/2,              % +Obj, +Options
   67	    is_pi/1,                    % @Term
   68	    is_op_type/2,               % +Atom, ?Type
   69					% Output routines
   70	    file//1,                    % +File, //
   71	    file//2,                    % +File, +Options, //
   72	    include//3,                 % +File, +Type, +Options //
   73	    tags//1,                    % +Tags, //
   74	    term//3,                    % +Text, +Term, +Bindings, //
   75	    file_header//2,             % +File, +Options, //
   76	    flagref//1,                 % +Flag
   77	    objects//2,                 % +Objects, +Options, //
   78	    object_ref//2,              % +Object, +Options, //
   79	    object_name//2,             % +Object, +Object
   80	    object_href/2,              % +Object, -URL
   81	    object_tree//3,             % +Tree, +Current, +Options
   82	    object_page//2,             % +Object, +Options, //
   83	    object_page_header//2,      % +File, +Options, //
   84	    object_synopsis//2,         % +Object, +Options, //
   85	    object_footer//2,           % +Object, +Options, //
   86	    object_page_footer//2,      % +Object, +Options, //
   87	    cite//1                     % +Citations
   88	  ]).   89% Get HTTP server infrastructure when available
   90:- if(exists_source(library(http/http_dispatch))).   91:- use_module(library(http/http_dispatch)).   92:- use_module(library(http/http_wrapper)).   93:- use_module(library(http/jquery)).   94
   95pldoc_server(true).
   96:- else.   97
   98:- multifile
   99    http:location/3.  100
  101http:location(pldoc_resource, '/pldoc/res', []).
  102
  103pldoc_server(false).
  104:- endif.  105
  106:- use_module(library(lists)).  107:- use_module(library(option)).  108:- use_module(library(uri)).  109:- use_module(library(readutil)).  110:- use_module(library(http/html_write)).  111:- use_module(library(http/http_path)).  112:- use_module(library(http/html_head)).  113:- use_module(library(http/term_html)).  114:- use_module(library(debug)).  115:- use_module(library(apply)).  116:- use_module(library(pairs)).  117:- use_module(library(filesex)).  118:- use_module(doc_process).  119:- use_module(doc_man).  120:- use_module(doc_modes).  121:- use_module(doc_wiki).  122:- use_module(doc_search).  123:- use_module(doc_index).  124:- use_module(doc_util).  125:- use_module(library(solution_sequences)).  126:- use_module(library(error)).  127:- use_module(library(occurs)).  128:- use_module(library(prolog_source)).  129:- use_module(library(prolog_xref)).  130
  131:- include(hooks).  132
  133
  134/** <module> PlDoc HTML backend
  135
  136This  module  translates  the  Herbrand   term  from  the  documentation
  137extracting module doc_wiki.pl into HTML+CSS.
  138
  139@tbd    Split put generation from computation as computation is reusable
  140	in other backends.
  141*/
  142
  143:- public
  144    args//1,                        % Called from \Term output created
  145    pred_dt//3,                     % by the wiki renderer
  146    section//2,
  147    tag//2.  148
  149
  150:- predicate_options(doc_for_wiki_file/2, 2,
  151		     [ edit(boolean)
  152		     ]).  153:- predicate_options(doc_hide_private/3, 3,
  154		     [module(atom), public(list), public_only(boolean)]).  155:- predicate_options(edit_button//2, 2,
  156		     [ edit(boolean)
  157		     ]).  158:- predicate_options(file//2, 2,
  159		     [ label(any),
  160		       absolute_path(atom),
  161		       href(atom),
  162		       map_extension(list),
  163		       files(list),
  164		       edit_handler(atom)
  165		     ]).  166:- predicate_options(file_header//2, 2,
  167		     [ edit(boolean),
  168		       files(list),
  169		       public_only(boolean)
  170		     ]).  171:- predicate_options(include//3, 3,
  172		     [ absolute_path(atom),
  173		       class(atom),
  174		       files(list),
  175		       href(atom),
  176		       label(any),
  177		       map_extension(list)
  178		     ]).  179:- predicate_options(object_edit_button//2, 2,
  180		     [ edit(boolean),
  181		       pass_to(pred_edit_button//2, 2)
  182		     ]).  183:- predicate_options(object_page//2, 2,
  184		     [ for(any),
  185		       header(boolean),
  186		       links(boolean),
  187		       no_manual(boolean),
  188		       try_manual(boolean),
  189		       search_in(oneof([all,app,man])),
  190		       search_match(oneof([name,summary])),
  191		       search_options(boolean)
  192		     ]).  193:- predicate_options(object_ref//2, 2,
  194		     [ files(list),
  195		       qualify(boolean),
  196		       style(oneof([number,title,number_title])),
  197		       secref_style(oneof([number,title,number_title]))
  198		     ]).  199:- predicate_options(object_synopsis//2, 2,
  200		     [ href(atom)
  201		     ]).  202:- predicate_options(pred_dt//3, 3,
  203		     [ edit(boolean)
  204		     ]).  205:- predicate_options(pred_edit_button//2, 2,
  206		     [ edit(boolean)
  207		     ]).  208:- predicate_options(predref//2, 2,
  209		     [ files(list),
  210		       prefer(oneof([manual,app])),
  211		       pass_to(object_ref/4, 2)
  212		     ]).  213:- predicate_options(private/2, 2,
  214		     [ module(atom),
  215		       public(list)
  216		     ]).  217:- predicate_options(source_button//2, 2,
  218		     [ files(list)
  219		     ]).  220
  221
  222		 /*******************************
  223		 *           RESOURCES          *
  224		 *******************************/
  225
  226:- if(pldoc_server(true)).  227:- html_resource(pldoc_css,
  228		 [ virtual(true),
  229		   requires([ pldoc_resource('pldoc.css')
  230			    ])
  231		 ]).  232:- html_resource(pldoc_resource('pldoc.js'),
  233		 [ requires([ jquery
  234			    ])
  235		 ]).  236:- html_resource(pldoc_js,
  237		 [ virtual(true),
  238		   requires([ pldoc_resource('pldoc.js')
  239			    ])
  240		 ]).  241:- html_resource(pldoc,
  242		 [ virtual(true),
  243		   requires([ pldoc_css,
  244			      pldoc_js
  245			    ])
  246		 ]).  247:- else.  248:- html_resource(pldoc_css, [virtual(true)]).  249:- html_resource(pldoc_resource('pldoc.js'), [virtual(true)]).  250:- html_resource(pldoc_js, [virtual(true)]).  251:- html_resource(pldoc, [virtual(true)]).  252:- endif.  253
  254
  255		 /*******************************
  256		 *       FILE PROCESSING        *
  257		 *******************************/
  258
  259%!  doc_for_file(+File, +Options) is det
  260%
  261%   HTTP  handler  that  writes  documentation  for  File  as  HTML.
  262%   Options:
  263%
  264%           * public_only(+Bool)
  265%           If =true= (default), only emit documentation for
  266%           exported predicates.
  267%
  268%           * edit(Bool)
  269%           If =true=, provide edit buttons. Default, these buttons
  270%           are suppressed.
  271%
  272%           * title(+Title)
  273%           Specify the page title.  Default is the base name of the
  274%           file.
  275%
  276%   @param File     Prolog file specification or xref source id.
  277
  278doc_for_file(FileSpec, Options) :-
  279    doc_file_objects(FileSpec, File, Objects, FileOptions, Options),
  280    doc_file_title(File, Title, FileOptions, Options),
  281    doc_write_page(
  282	pldoc(file(File, Title)),
  283	title(Title),
  284	\prolog_file(File, Objects, FileOptions, Options),
  285	Options).
  286
  287doc_file_title(_, Title, _, Options) :-
  288    option(title(Title), Options),
  289    !.
  290doc_file_title(File, Title, FileOptions, _) :-
  291    memberchk(file(Title0, _Comment), FileOptions),
  292    !,
  293    file_base_name(File, Base),
  294    atomic_list_concat([Base, ' -- ', Title0], Title).
  295doc_file_title(File, Title, _, _) :-
  296    file_base_name(File, Title).
  297
  298:- html_meta doc_write_page(+, html, html, +).  299
  300doc_write_page(Style, Head, Body, Options) :-
  301    option(files(_), Options),
  302    !,
  303    phrase(page(Style, Head, Body), HTML),
  304    print_html(HTML).
  305doc_write_page(Style, Head, Body, _) :-
  306    reply_html_page(Style, Head, Body).
  307
  308
  309prolog_file(File, Objects, FileOptions, Options) -->
  310    { b_setval(pldoc_file, File),   % TBD: delete?
  311      file_directory_name(File, Dir)
  312    },
  313    html([ \doc_resources(Options),
  314	   \doc_links(Dir, FileOptions),
  315	   \file_header(File, FileOptions)
  316	 | \objects(Objects, FileOptions)
  317	 ]),
  318    undocumented(File, Objects, FileOptions).
  319
  320%!  doc_resources(+Options)// is det.
  321%
  322%   Include required resources (CSS, JS) into  the output. The first
  323%   clause supports doc_files.pl. A bit hacky ...
  324
  325doc_resources(Options) -->
  326    { option(resource_directory(ResDir), Options),
  327      nb_current(pldoc_output, OutputFile),
  328      !,
  329      directory_file_path(ResDir, 'pldoc.css', Res),
  330      relative_file_name(Res, OutputFile, Ref)
  331    },
  332    html_requires(Ref).
  333doc_resources(Options) -->
  334    { option(html_resources(Resoures), Options, pldoc)
  335    },
  336    html_requires(Resoures).
  337
  338
  339%!  doc_file_objects(+FileSpec, -File, -Objects, -FileOptions, +Options) is det.
  340%
  341%   Extracts  relevant  information  for  FileSpec  from  the  PlDoc
  342%   database.  FileOptions contains:
  343%
  344%           * file(Title:string, Comment:string)
  345%           * module(Module:atom)
  346%           * public(Public:list(predicate_indicator)
  347%
  348%   Objects contains
  349%
  350%           * doc(PI:predicate_indicator, File:Line, Comment)
  351%
  352%   We distinguish three different states for FileSpec:
  353%
  354%     1. File was cross-referenced with collection enabled.  All
  355%        information is in the xref database.
  356%     2. File was loaded. If comments are not loaded,
  357%        cross-reference the file, while _storing_ the comments
  358%        as the compiler would do.
  359%     3. Neither of the above.  In this case we cross-reference the
  360%        file.
  361%
  362%   @param FileSpec File specification as used for load_files/2.
  363%   @param File     Prolog canonical filename
  364
  365doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  366    xref_current_source(FileSpec),
  367    xref_option(FileSpec, comments(collect)),
  368    !,
  369    File = FileSpec,
  370    findall(Object, xref_doc_object(File, Object), Objects0),
  371    reply_file_objects(File, Objects0, Objects, FileOptions, Options).
  372doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  373    absolute_file_name(FileSpec, File,
  374		       [ file_type(prolog),
  375			 access(read)
  376		       ]),
  377    source_file(File),
  378    !,
  379    ensure_doc_objects(File),
  380    Pos = File:Line,
  381    findall(Line-doc(Obj,Pos,Comment),
  382	    doc_comment(Obj, Pos, _, Comment), Pairs),
  383    sort(Pairs, Pairs1),            % remove duplicates
  384    keysort(Pairs1, ByLine),
  385    pairs_values(ByLine, Objs0),
  386    reply_file_objects(File, Objs0, Objects, FileOptions, Options).
  387doc_file_objects(FileSpec, File, Objects, FileOptions, Options) :-
  388    absolute_file_name(FileSpec, File,
  389		       [ file_type(prolog),
  390			 access(read)
  391		       ]),
  392    xref_source(File, [silent(true)]),
  393    findall(Object, xref_doc_object(File, Object), Objects0),
  394    reply_file_objects(File, Objects0, Objects, FileOptions, Options).
  395
  396
  397reply_file_objects(File, Objs0, Objects, FileOptions, Options) :-
  398    module_info(File, ModuleOptions, Options),
  399    file_info(Objs0, Objs1, FileOptions, ModuleOptions),
  400    doc_hide_private(Objs1, ObjectsSelf, ModuleOptions),
  401    include_reexported(ObjectsSelf, Objects1, File, FileOptions),
  402    remove_doc_duplicates(Objects1, Objects, []).
  403
  404remove_doc_duplicates([], [], _).
  405remove_doc_duplicates([H|T0], [H|T], Seen) :-
  406    H = doc(_, _, Comment),
  407    \+ memberchk(Comment, Seen),
  408    !,
  409    remove_doc_duplicates(T0, T, [Comment|Seen]).
  410remove_doc_duplicates([_|T0], T, Seen) :-
  411    remove_doc_duplicates(T0, T, Seen).
  412
  413include_reexported(SelfObjects, Objects, File, Options) :-
  414    option(include_reexported(true), Options),
  415    option(module(Module), Options),
  416    option(public(Exports), Options),
  417    select_undocumented(Exports, Module, SelfObjects, Undoc),
  418    re_exported_doc(Undoc, File, Module, REObjs, _),
  419    REObjs \== [],
  420    !,
  421    append(SelfObjects, REObjs, Objects).
  422include_reexported(Objects, Objects, _, _).
  423
  424
  425%!  xref_doc_object(File, DocObject) is nondet.
  426
  427xref_doc_object(File, doc(M:module(Title),File:0,Comment)) :-
  428    xref_comment(File, Title, Comment),
  429    xref_module(File, M).
  430xref_doc_object(File, doc(M:Name/Arity,File:0,Comment)) :-
  431    xref_comment(File, Head, _Summary, Comment),
  432    xref_module(File, Module),
  433    strip_module(Module:Head, M, Plain),
  434    functor(Plain, Name, Arity).
  435
  436%!  ensure_doc_objects(+File) is det.
  437%
  438%   Ensure we have documentation about File.  If we have no comments
  439%   for the file because it was loaded before comment collection was
  440%   enabled, run the cross-referencer on it  to collect the comments
  441%   and meta-information.
  442%
  443%   @param File is a canonical filename that is loaded.
  444
  445:- dynamic
  446    no_comments/2.  447
  448ensure_doc_objects(File) :-
  449    source_file(File),
  450    !,
  451    (   doc_file_has_comments(File)
  452    ->  true
  453    ;   no_comments(File, TimeChecked),
  454	time_file(File, TimeChecked)
  455    ->  true
  456    ;   xref_source(File, [silent(true), comments(store)]),
  457	retractall(no_comments(File, _)),
  458	(   doc_file_has_comments(File)
  459	->  true
  460	;   time_file(File, TimeChecked),
  461	    assertz(no_comments(File, TimeChecked))
  462	)
  463    ).
  464ensure_doc_objects(File) :-
  465    xref_source(File, [silent(true)]).
  466
  467%!  module_info(+File, -ModuleOptions, +OtherOptions) is det.
  468%
  469%   Add options module(Name),  public(Exports)   to  OtherOptions if
  470%   File is a module file.
  471
  472module_info(File, [module(Module), public(Exports)|Options], Options) :-
  473    module_property(Module, file(File)),
  474    !,
  475    module_property(Module, exports(Exports)).
  476module_info(File, [module(Module), public(Exports)|Options], Options) :-
  477    xref_module(File, Module),
  478    !,
  479    findall(PI, xref_exported_pi(File, PI), Exports).
  480module_info(_, Options, Options).
  481
  482xref_exported_pi(Src, Name/Arity) :-
  483    xref_exported(Src, Head),
  484    functor(Head, Name, Arity).
  485
  486%!  doc_hide_private(+Objs, +Public, +Options)
  487%
  488%   Remove the private objects from Objs according to Options.
  489
  490doc_hide_private(Objs, Objs, Options) :-
  491    option(public_only(false), Options, true),
  492    !.
  493doc_hide_private(Objs0, Objs, Options) :-
  494    hide_private(Objs0, Objs, Options).
  495
  496hide_private([], [], _).
  497hide_private([H|T0], T, Options) :-
  498    obj(H, Obj),
  499    private(Obj, Options),
  500    !,
  501    hide_private(T0, T, Options).
  502hide_private([H|T0], [H|T], Options) :-
  503    hide_private(T0, T, Options).
  504
  505%!  obj(+Term, -Object) is det.
  506%
  507%   Extract the documented  object  from   its  environment.  It  is
  508%   assumed to be the first term. Note  that if multiple objects are
  509%   described by the same comment Term is a list.
  510
  511obj(doc(Obj0, _Pos, _Summary), Obj) :-
  512    !,
  513    (   Obj0 = [Obj|_]
  514    ->  true
  515    ;   Obj = Obj0
  516    ).
  517obj(Obj0, Obj) :-
  518    (   Obj0 = [Obj|_]
  519    ->  true
  520    ;   Obj = Obj0
  521    ).
  522
  523
  524%!  private(+Obj, +Options) is semidet.
  525%
  526%   True if Obj is not  exported   from  Options. This means Options
  527%   defined a module and Obj is  not   member  of the exports of the
  528%   module.
  529
  530:- multifile
  531    prolog:doc_is_public_object/1.  532
  533private(Object, _Options):-
  534    prolog:doc_is_public_object(Object), !, fail.
  535private(Module:PI, Options) :-
  536    multifile(Module:PI, Options), !, fail.
  537private(Module:PI, Options) :-
  538    public(Module:PI, Options), !, fail.
  539private(Module:PI, Options) :-
  540    option(module(Module), Options),
  541    option(public(Public), Options),
  542    !,
  543    \+ ( member(PI2, Public),
  544	 eq_pi(PI, PI2)
  545       ).
  546private(Module:PI, _Options) :-
  547    module_property(Module, file(_)),      % A loaded module
  548    !,
  549    module_property(Module, exports(Exports)),
  550    \+ ( member(PI2, Exports),
  551	 eq_pi(PI, PI2)
  552       ).
  553private(Module:PI, _Options) :-
  554    \+ (pi_to_head(PI, Head),
  555	xref_exported(Source, Head),
  556	xref_module(Source, Module)).
  557
  558%!  prolog:doc_is_public_object(+Object) is semidet.
  559%
  560%   Hook that allows objects  to  be   displayed  with  the  default
  561%   public-only view.
  562
  563%!  multifile(+Obj, +Options) is semidet.
  564%
  565%   True if Obj is a multifile predicate.
  566
  567multifile(Obj, _Options) :-
  568    strip_module(user:Obj, Module, PI),
  569    pi_to_head(PI, Head),
  570    (   predicate_property(Module:Head, multifile)
  571    ;   xref_module(Source, Module),
  572	xref_defined(Source, Head, multifile(_Line))
  573    ),
  574    !.
  575
  576%!  public(+Options, +Options)
  577%
  578%   True if Obj is declared using public/1.
  579
  580public(Obj, _Options) :-
  581    strip_module(user:Obj, Module, PI),
  582    pi_to_head(PI, Head),
  583    (   predicate_property(Module:Head, public)
  584    ;   xref_module(Source, Module),
  585	xref_defined(Source, Head, public(_Line))
  586    ),
  587    !.
  588
  589pi_to_head(Var, _) :-
  590    var(Var), !, fail.
  591pi_to_head(Name/Arity, Term) :-
  592    functor(Term, Name, Arity).
  593pi_to_head(Name//DCGArity, Term) :-
  594    Arity is DCGArity+2,
  595    functor(Term, Name, Arity).
  596
  597%!  file_info(+Comments, -RestComment, -FileOptions, +OtherOptions) is det.
  598%
  599%   Add options file(Title, Comment) to OtherOptions if available.
  600
  601file_info(Comments, RestComments, [file(Title, Comment)|Opts], Opts) :-
  602    select(doc(_:module(Title),_,Comment), Comments, RestComments),
  603    !.
  604file_info(Comments, Comments, Opts, Opts).
  605
  606
  607%!  file_header(+File, +Options)// is det.
  608%
  609%   Create the file header.
  610
  611file_header(File, Options) -->
  612    { memberchk(file(Title, Comment), Options),
  613      !,
  614      file_base_name(File, Base)
  615    },
  616    file_title([Base, ' -- ', Title], File, Options),
  617    { is_structured_comment(Comment, Prefixes),
  618      string_codes(Comment, Codes),
  619      indented_lines(Codes, Prefixes, Lines),
  620      section_comment_header(Lines, _Header, Lines1),
  621      wiki_lines_to_dom(Lines1, [], DOM)
  622    },
  623    html(DOM).
  624file_header(File, Options) -->
  625    { file_base_name(File, Base)
  626    },
  627    file_title([Base], File, Options).
  628
  629
  630%!  file_title(+Title:list, +File, +Options)// is det
  631%
  632%   Emit the file-header and manipulation buttons.
  633
  634file_title(Title, File, Options) -->
  635    prolog:doc_file_title(Title, File, Options),
  636    !.
  637file_title(Title, File, Options) -->
  638    { file_base_name(File, Base)
  639    },
  640    html(h1(class(file),
  641	    [ span(style('float:right'),
  642		   [ \reload_button(File, Base, Options),
  643		     \zoom_button(Base, Options),
  644		     \source_button(Base, Options),
  645		     \edit_button(File, Options)
  646		   ])
  647	    | Title
  648	    ])).
  649
  650
  651%!  reload_button(+File, +Base, +Options)// is det.
  652%
  653%   Create a button for  reloading  the   sources  and  updating the
  654%   documentation page. Note that the  button   is  not shown if the
  655%   file is not loaded because we do  not want to load files through
  656%   the documentation system.
  657
  658reload_button(File, _Base, Options) -->
  659    { \+ source_file(File),
  660      \+ option(files(_), Options)
  661    },
  662    !,
  663    html(span(class(file_anot), '[not loaded]')).
  664reload_button(_File, Base, Options) -->
  665    { option(edit(true), Options),
  666      !,
  667      option(public_only(Public), Options, true)
  668    },
  669    html(a(href(Base+[reload(true), public_only(Public)]),
  670	   img([ class(action),
  671		 alt('Reload'),
  672		 title('Make & Reload'),
  673		 src(location_by_id(pldoc_resource)+'reload.png')
  674	       ]))).
  675reload_button(_, _, _) --> [].
  676
  677%!  edit_button(+File, +Options)// is det.
  678%
  679%   Create an edit button  for  File.   If  the  button  is clicked,
  680%   JavaScript sends a message to the   server without modifying the
  681%   current page.  JavaScript code is in the file pldoc.js.
  682
  683edit_button(File, Options) -->
  684    { option(edit(true), Options)
  685    },
  686    !,
  687    html(a([ onClick('HTTPrequest(\'' +
  688		     location_by_id(pldoc_edit) + [file(File)] +
  689		     '\')')
  690	   ],
  691	   img([ class(action),
  692		 alt(edit),
  693		 title('Edit file'),
  694		 src(location_by_id(pldoc_resource)+'edit.png')
  695	     ]))).
  696edit_button(_, _) -->
  697    [].
  698
  699
  700%!  zoom_button(BaseName, +Options)// is det.
  701%
  702%   Add zoom in/out button to show/hide the private documentation.
  703
  704zoom_button(_, Options) -->
  705    { option(files(_Map), Options) },
  706    !.    % generating files
  707zoom_button(Base, Options) -->
  708    {   (   option(public_only(true), Options, true)
  709	->  Zoom = 'public.png',
  710	    Alt = 'Public',
  711	    Title = 'Click to include private',
  712	    PublicOnly = false
  713	;   Zoom = 'private.png',
  714	    Alt = 'All predicates',
  715	    Title = 'Click to show exports only',
  716	    PublicOnly = true
  717	)
  718    },
  719    html(a(href(Base+[public_only(PublicOnly)]),
  720	   img([ class(action),
  721		 alt(Alt),
  722		 title(Title),
  723		 src(location_by_id(pldoc_resource)+Zoom)
  724	       ]))).
  725
  726
  727%!  source_button(+File, +Options)// is det.
  728%
  729%   Add show-source button.
  730
  731source_button(_File, Options) -->
  732    { option(files(_Map), Options) },
  733    !.    % generating files
  734source_button(File, _Options) -->
  735    { (   is_absolute_file_name(File)
  736      ->  doc_file_href(File, HREF0)
  737      ;   HREF0 = File
  738      )
  739    },
  740    html(a(href(HREF0+[show(src)]),
  741	   img([ class(action),
  742		 alt('Show source'),
  743		 title('Show source'),
  744		 src(location_by_id(pldoc_resource)+'source.png')
  745	       ]))).
  746
  747
  748%!  objects(+Objects:list, +Options)// is det.
  749%
  750%   Emit the documentation body.  Options includes:
  751%
  752%     * navtree(+Boolean)
  753%     If =true=, provide a navitation tree.
  754
  755objects(Objects, Options) -->
  756    { option(navtree(true), Options),
  757      !,
  758      objects_nav_tree(Objects, Tree)
  759    },
  760    html([ div(class(navtree),
  761	       div(class(navwindow),
  762		   \nav_tree(Tree, Objects, Options))),
  763	   div(class(navcontent),
  764	       \objects_nt(Objects, Options))
  765	 ]).
  766objects(Objects, Options) -->
  767    objects_nt(Objects, Options).
  768
  769objects_nt(Objects, Options) -->
  770    objects(Objects, [body], Options).
  771
  772objects([], Mode, _) -->
  773    pop_mode(body, Mode, _).
  774objects([Obj|T], Mode, Options) -->
  775    object(Obj, Mode, Mode1, Options),
  776    objects(T, Mode1, Options).
  777
  778%!  object(+Spec, +ModeIn, -ModeOut, +Options)// is det.
  779%
  780%   Emit the documentation of a single object.
  781%
  782%   @param  Spec is one of doc(Obj,Pos,Comment), which is used
  783%           to list the objects documented in a file or a plain
  784%           Obj, used for documenting the object regardless of
  785%           its location.
  786
  787object(doc(Obj,Pos,Comment), Mode0, Mode, Options) -->
  788    !,
  789    object(Obj, [Pos-Comment], Mode0, Mode, [scope(file)|Options]).
  790object(Obj, Mode0, Mode, Options) -->
  791    { findall(Pos-Comment,
  792	      doc_comment(Obj, Pos, _Summary, Comment),
  793	      Pairs)
  794    },
  795    !,
  796    { b_setval(pldoc_object, Obj) },
  797    object(Obj, Pairs, Mode0, Mode, Options).
  798
  799object(Obj, Pairs, Mode0, Mode, Options) -->
  800    { is_pi(Obj),
  801      !,
  802      maplist(pred_dom(Obj, Options), Pairs, DOMS),
  803      append(DOMS, DOM)
  804    },
  805    need_mode(dl, Mode0, Mode),
  806    html(DOM).
  807object([Obj|_Same], Pairs, Mode0, Mode, Options) -->
  808    !,
  809    object(Obj, Pairs, Mode0, Mode, Options).
  810object(Obj, _Pairs, Mode, Mode, _Options) -->
  811    { debug(pldoc, 'Skipped ~p', [Obj]) },
  812    [].
  813
  814pred_dom(Obj, Options, Pos-Comment, DOM) :-
  815    is_structured_comment(Comment, Prefixes),
  816    string_codes(Comment, Codes),
  817    indented_lines(Codes, Prefixes, Lines),
  818    strip_module(user:Obj, Module, _),
  819    process_modes(Lines, Module, Pos, Modes, Args, Lines1),
  820    (   private(Obj, Options)
  821    ->  Class = privdef             % private definition
  822    ;   multifile(Obj, Options)
  823    ->  (   option(scope(file), Options)
  824	->  (   more_doc(Obj, Pos)
  825	    ->  Class = multidef(object(Obj))
  826	    ;   Class = multidef
  827	    )
  828	;   Class = multidef(file((Pos)))
  829	)
  830    ;   public(Obj, Options)
  831    ->  Class = publicdef           % :- public definition
  832    ;   Class = pubdef              % exported definition
  833    ),
  834    (   Obj = Module:_
  835    ->  POptions = [module(Module)|Options]
  836    ;   POptions = Options
  837    ),
  838    Pos = File:Line,
  839    DTOptions = [file(File),line(Line)|POptions],
  840    DOM = [\pred_dt(Modes, Class, DTOptions), dd(class=defbody, DOM1)],
  841    wiki_lines_to_dom(Lines1, Args, DOM0),
  842    strip_leading_par(DOM0, DOM1).
  843
  844more_doc(Obj, File:_) :-
  845    doc_comment(Obj, File2:_, _, _),
  846    File2 \== File,
  847    !.
  848
  849%!  need_mode(+Mode:atom, +Stack:list, -NewStack:list)// is det.
  850%
  851%   While predicates are part of a   description  list, sections are
  852%   not and we therefore  need  to   insert  <dl>...</dl>  into  the
  853%   output. We do so by demanding  an outer environment and push/pop
  854%   the required elements.
  855
  856need_mode(Mode, Stack, Stack) -->
  857    { Stack = [Mode|_] },
  858    !,
  859    [].
  860need_mode(Mode, Stack, Rest) -->
  861    { memberchk(Mode, Stack)
  862    },
  863    !,
  864    pop_mode(Mode, Stack, Rest).
  865need_mode(Mode, Stack, [Mode|Stack]) -->
  866    !,
  867    html_begin(Mode).
  868
  869pop_mode(Mode, Stack, Stack) -->
  870    { Stack = [Mode|_] },
  871    !,
  872    [].
  873pop_mode(Mode, [H|Rest0], Rest) -->
  874    html_end(H),
  875    pop_mode(Mode, Rest0, Rest).
  876
  877%!  undocumented(+File, +Objects, +Options)// is det.
  878%
  879%   Describe undocumented predicates if the file is a module file.
  880
  881undocumented(File, Objs, Options) -->
  882    { memberchk(module(Module), Options),
  883      memberchk(public(Exports), Options),
  884      select_undocumented(Exports, Module, Objs, Undoc),
  885      re_exported_doc(Undoc, File, Module, REObjs, ReallyUnDoc)
  886    },
  887    !,
  888    re_exported_doc(REObjs, Options),
  889    undocumented(ReallyUnDoc, Options).
  890undocumented(_, _, _) -->
  891    [].
  892
  893re_exported_doc([], _) --> !.
  894re_exported_doc(Objs, Options) -->
  895    reexport_header(Objs, Options),
  896    objects(Objs, Options).
  897
  898reexport_header(_, Options) -->
  899    { option(reexport_header(true), Options, true)
  900    },
  901    !,
  902    html([ h2(class(wiki), 'Re-exported predicates'),
  903	   p([ "The following predicates are exported from this file \c
  904                while their implementation is defined in imported modules \c
  905                or non-module files loaded by this module."
  906	     ])
  907	 ]).
  908reexport_header(_, _) -->
  909    [].
  910
  911undocumented([], _) --> !.
  912undocumented(UnDoc, Options) -->
  913    html([ h2(class(undoc), 'Undocumented predicates'),
  914	   p(['The following predicates are exported, but not ',
  915	      'or incorrectly documented.'
  916	     ]),
  917	   dl(class(undoc),
  918	      \undocumented_predicates(UnDoc, Options))
  919	 ]).
  920
  921
  922undocumented_predicates([], _) -->
  923    [].
  924undocumented_predicates([H|T], Options) -->
  925    undocumented_pred(H, Options),
  926    undocumented_predicates(T, Options).
  927
  928undocumented_pred(Name/Arity, Options) -->
  929    { functor(Head, Name, Arity) },
  930    html(dt(class=undoc, \pred_mode(Head, [], _, Options))).
  931
  932select_undocumented([], _, _, []).
  933select_undocumented([PI|T0], M, Objs, [PI|T]) :-
  934    is_pi(PI),
  935    \+ in_doc(M:PI, Objs),
  936    !,
  937    select_undocumented(T0, M, Objs, T).
  938select_undocumented([_|T0], M, Objs, T) :-
  939    select_undocumented(T0, M, Objs, T).
  940
  941in_doc(PI, Objs) :-
  942    member(doc(O,_,_), Objs),
  943    (   is_list(O)
  944    ->  member(O2, O),
  945	eq_pi(PI, O2)
  946    ;   eq_pi(PI, O)
  947    ).
  948
  949
  950%!  eq_pi(PI1, PI2) is semidet.
  951%
  952%   True if PI1 and PI2 refer to the same predicate.
  953
  954eq_pi(PI, PI) :- !.
  955eq_pi(M:PI1, M:PI2) :-
  956    atom(M),
  957    !,
  958    eq_pi(PI1, PI2).
  959eq_pi(Name/A, Name//DCGA) :-
  960    A =:= DCGA+2,
  961    !.
  962eq_pi(Name//DCGA, Name/A) :-
  963    A =:= DCGA+2.
  964
  965%!  is_pi(@Term) is semidet.
  966%
  967%   True if Term is a predicate indicator.
  968
  969is_pi(Var) :-
  970    var(Var),
  971    !,
  972    fail.
  973is_pi(_:PI) :-
  974    !,
  975    is_pi(PI).
  976is_pi(_/_).
  977is_pi(_//_).
  978
  979
  980%!  re_exported_doc(+Undoc:list(pi), +File:atom, +Module:atom,
  981%!                  -ImportedDoc, -ReallyUnDoc:list(pi))
  982
  983re_exported_doc([], _, _, [], []).
  984re_exported_doc([PI|T0], File, Module, [doc(Orig:PI,Pos,Comment)|ObjT], UnDoc) :-
  985    pi_to_head(PI, Head),
  986    (   predicate_property(Module:Head, imported_from(Orig))
  987    ->  true
  988    ;   predicate_property(Module:Head, exported)
  989    ->  Orig = Module
  990    ;   xref_defined(File, Head, imported(File2)),
  991	ensure_doc_objects(File2),
  992	xref_module(File2, Orig)
  993    ),
  994    doc_comment(Orig:PI, Pos, _, Comment),
  995    !,
  996    re_exported_doc(T0, File, Module, ObjT, UnDoc).
  997re_exported_doc([PI|T0], File, Module, REObj, [PI|UnDoc]) :-
  998    re_exported_doc(T0, File, Module, REObj, UnDoc).
  999
 1000
 1001		 /*******************************
 1002		 *      SINGLE OBJECT PAGE      *
 1003		 *******************************/
 1004
 1005%!  object_page(+Obj, +Options)// is semidet.
 1006%
 1007%   Generate an HTML page describing Obj.  The top presents the file
 1008%   the object is documented in and a search-form.  Options:
 1009%
 1010%       * header(+Boolean)
 1011%       Show the navigation and search header.
 1012
 1013object_page(Obj, Options) -->
 1014    prolog:doc_object_page(Obj, Options),
 1015    !,
 1016    object_page_footer(Obj, Options).
 1017object_page(Obj, Options) -->
 1018    { doc_comment(Obj, File:_Line, _Summary, _Comment)
 1019    },
 1020    !,
 1021    (   { \+ ( doc_comment(Obj, File2:_, _, _),
 1022	       File2 \== File )
 1023	}
 1024    ->  html([ \html_requires(pldoc),
 1025	       \object_page_header(File, Options),
 1026	       \object_synopsis(Obj, []),
 1027	       \objects([Obj], Options)
 1028	     ])
 1029    ;   html([ \html_requires(pldoc),
 1030	       \object_page_header(-, Options),
 1031	       \objects([Obj], [synopsis(true)|Options])
 1032	     ])
 1033    ),
 1034    object_page_footer(Obj, Options).
 1035object_page(M:Name/Arity, Options) -->          % specified module, but public
 1036    { functor(Head, Name, Arity),
 1037      (   predicate_property(M:Head, exported)
 1038      ->  module_property(M, class(library))
 1039      ;   \+ predicate_property(M:Head, defined)
 1040      )
 1041    },
 1042    prolog:doc_object_page(Name/Arity, Options),
 1043    !,
 1044    object_page_footer(Name/Arity, Options).
 1045
 1046object_page_header(File, Options) -->
 1047    prolog:doc_page_header(file(File), Options),
 1048    !.
 1049object_page_header(File, Options) -->
 1050    { option(header(true), Options, true) },
 1051    !,
 1052    html(div(class(navhdr),
 1053	     [ div(class(jump), \file_link(File)),
 1054	       div(class(search), \search_form(Options)),
 1055	       br(clear(right))
 1056	     ])).
 1057object_page_header(_, _) --> [].
 1058
 1059file_link(-) -->
 1060    !,
 1061    places_menu(-).
 1062file_link(File) -->
 1063    { file_directory_name(File, Dir)
 1064    },
 1065    places_menu(Dir),
 1066    html([ div(a(href(location_by_id(pldoc_doc)+File), File))
 1067	 ]).
 1068
 1069%!  object_footer(+Obj, +Options)// is det.
 1070%
 1071%   Call the hook prolog:doc_object_footer//2. This hook will be used to
 1072%   deal with examples.
 1073
 1074object_footer(Obj, Options) -->
 1075    prolog:doc_object_footer(Obj, Options),
 1076    !.
 1077object_footer(_, _) --> [].
 1078
 1079
 1080%!  object_page_footer(+Obj, +Options)// is det.
 1081%
 1082%   Call the hook prolog:doc_object_page_footer//2. This hook will
 1083%   be used to deal with annotations.
 1084
 1085object_page_footer(Obj, Options) -->
 1086    prolog:doc_object_page_footer(Obj, Options),
 1087    !.
 1088object_page_footer(_, _) --> [].
 1089
 1090
 1091%!  object_synopsis(Obj, Options)// is det.
 1092%
 1093%   Provide additional information  about  Obj.   Note  that  due to
 1094%   reexport facilities, predicates may be   available from multiple
 1095%   modules.
 1096%
 1097%   @tbd Currently we provide a synopsis   for the one where the
 1098%   definition resides. This is not   always  correct. Notably there
 1099%   are cases where multiple implementation modules are bundled in a
 1100%   larger interface that is the `preferred' module.
 1101
 1102object_synopsis(Name/Arity, _) -->
 1103    { functor(Head, Name, Arity),
 1104      predicate_property(system:Head, built_in)
 1105    },
 1106    synopsis([span(class(builtin), 'built-in')]).
 1107object_synopsis(Name/Arity, Options) -->
 1108    !,
 1109    object_synopsis(_:Name/Arity, Options).
 1110object_synopsis(M:Name/Arity, Options) -->
 1111    { functor(Head, Name, Arity),
 1112      (   option(source(Spec), Options)
 1113      ->  absolute_file_name(Spec, File,
 1114			     [ access(read),
 1115			       file_type(prolog),
 1116			       file_errors(fail)
 1117			     ])
 1118      ;   predicate_property(M:Head, exported),
 1119	  \+ predicate_property(M:Head, imported_from(_)),
 1120	  module_property(M, file(File)),
 1121	  file_name_on_path(File, Spec)
 1122      ),
 1123      !,
 1124      unquote_filespec(Spec, Unquoted),
 1125      (   predicate_property(Head, autoload(FileBase)),
 1126	  file_name_extension(FileBase, _Ext, File)
 1127      ->  Extra = [span(class(autoload), '(can be autoloaded)')]
 1128      ;   Extra = []
 1129      )
 1130    },
 1131    (   { option(href(HREF), Options) }
 1132    ->  synopsis([code([':- use_module(',a(href(HREF), '~q'-[Unquoted]),').'])|Extra])
 1133    ;   synopsis([code(':- use_module(~q).'-[Unquoted])|Extra])
 1134    ).
 1135object_synopsis(Name//Arity, Options) -->
 1136    !,
 1137    { DCGArity is Arity+2 },
 1138    object_synopsis(Name/DCGArity, Options).
 1139object_synopsis(Module:Name//Arity, Options) -->
 1140    !,
 1141    { DCGArity is Arity+2 },
 1142    object_synopsis(Module:Name/DCGArity, Options).
 1143object_synopsis(f(_/_), _) -->
 1144    synopsis(span(class(function),
 1145		  [ 'Arithmetic function (see ',
 1146		    \object_ref(is/2, []),
 1147		    ')'
 1148		  ])).
 1149object_synopsis(c(Func), _) -->
 1150    {   sub_atom(Func, 0, _, _, 'PL_')
 1151    ;   sub_atom(Func, 0, _, _, 'S')
 1152    },
 1153    !,
 1154    synopsis([span(class(cfunc), 'C-language interface function')]).
 1155object_synopsis(_, _) --> [].
 1156
 1157synopsis(Text) -->
 1158    html(div(class(synopsis),
 1159	     [ span(class('synopsis-hdr'), 'Availability:')
 1160	     | Text
 1161	     ])).
 1162
 1163%!  unquote_filespec(+Spec, -Unquoted) is det.
 1164%
 1165%   Translate       e.g.       library('semweb/rdf_db')         into
 1166%   library(semweb/rdf_db).
 1167
 1168unquote_filespec(Spec, Unquoted) :-
 1169    compound(Spec),
 1170    Spec =.. [Alias,Path],
 1171    atom(Path),
 1172    atomic_list_concat(Parts, /, Path),
 1173    maplist(need_no_quotes, Parts),
 1174    !,
 1175    parts_to_path(Parts, UnquotedPath),
 1176    Unquoted =.. [Alias, UnquotedPath].
 1177unquote_filespec(Spec, Spec).
 1178
 1179need_no_quotes(Atom) :-
 1180    format(atom(A), '~q', [Atom]),
 1181    \+ sub_atom(A, 0, _, _, '\'').
 1182
 1183parts_to_path([One], One) :- !.
 1184parts_to_path(List, More/T) :-
 1185    (   append(H, [T], List)
 1186    ->  parts_to_path(H, More)
 1187    ).
 1188
 1189
 1190		 /*******************************
 1191		 *             PRINT            *
 1192		 *******************************/
 1193
 1194%!  doc_write_html(+Out:stream, +Title:atomic, +DOM) is det.
 1195%
 1196%   Write HTML for the documentation page DOM using Title to Out.
 1197
 1198doc_write_html(Out, Title, Doc) :-
 1199    doc_page_dom(Title, Doc, DOM),
 1200    phrase(html(DOM), Tokens),
 1201    print_html_head(Out),
 1202    print_html(Out, Tokens).
 1203
 1204%!  doc_page_dom(+Title, +Body, -DOM) is det.
 1205%
 1206%   Create the complete HTML DOM from the   Title  and Body. It adds
 1207%   links to the style-sheet and javaScript files.
 1208
 1209doc_page_dom(Title, Body, DOM) :-
 1210    DOM = html([ head([ title(Title),
 1211			link([ rel(stylesheet),
 1212			       type('text/css'),
 1213			       href(location_by_id(pldoc_resource)+'pldoc.css')
 1214			     ]),
 1215			script([ src(location_by_id(pldoc_resource)+'pldoc.js'),
 1216				 type('text/javascript')
 1217			       ], [])
 1218		      ]),
 1219		 body(Body)
 1220	       ]).
 1221
 1222%!  print_html_head(+Out:stream) is det.
 1223%
 1224%   Print the =DOCTYPE= line.
 1225
 1226print_html_head(Out) :-
 1227    format(Out,
 1228	   '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" \c
 1229	       "http://www.w3.org/TR/html4/strict.dtd">~n', []).
 1230
 1231% Rendering rules
 1232%
 1233% These rules translate \-terms produced by wiki.pl
 1234
 1235%!  tags(+Tags)// is det.
 1236%
 1237%   Emit the @tag tags of a description. Tags is produced by tags/3.
 1238%
 1239%   @see combine_tags/2.
 1240
 1241tags(Tags) -->
 1242    html(dl(class=tags, Tags)).
 1243
 1244%!  tag(+Tag, +Values:list)// is det.
 1245%
 1246%   Called from \tag(Name, Values) terms produced by doc_wiki.pl.
 1247
 1248tag(Tag, Values) -->
 1249    {   doc_tag_title(Tag, Title),
 1250	atom_concat('keyword-', Tag, Class)
 1251    },
 1252    html([ dt(class=Class, Title),
 1253	   \tag_values(Values, Class)
 1254	 ]).
 1255
 1256tag_values([], _) -->
 1257    [].
 1258tag_values([H|T], Class) -->
 1259    html(dd(class=Class, ['- '|H])),
 1260    tag_values(T, Class).
 1261
 1262
 1263%!  doc_tag_title(+Tag, -Title) is det.
 1264%
 1265%   Title is the name to use for Tag in the generated documentation.
 1266
 1267doc_tag_title(Tag, Title) :-
 1268    tag_title(Tag, Title),
 1269    !.
 1270doc_tag_title(Tag, Tag).
 1271
 1272tag_title(compat, 'Compatibility').
 1273tag_title(tbd,    'To be done').
 1274tag_title(see,    'See also').
 1275tag_title(error,  'Errors').
 1276
 1277%!  args(+Params:list) is det.
 1278%
 1279%   Called from \args(List) created by   doc_wiki.pl.  Params is a
 1280%   list of arg(Name, Descr).
 1281
 1282args(Params) -->
 1283    html([ dt(class=tag, 'Arguments:'),
 1284	   dd(table(class=arglist,
 1285		    \arg_list(Params)))
 1286	 ]).
 1287
 1288arg_list([]) -->
 1289    [].
 1290arg_list([H|T]) -->
 1291    argument(H),
 1292    arg_list(T).
 1293
 1294argument(arg(Name,Descr)) -->
 1295    html(tr([td(var(Name)), td(class=argdescr, ['- '|Descr])])).
 1296
 1297
 1298		 /*******************************
 1299		 *         NAVIGATION TREE      *
 1300		 *******************************/
 1301
 1302%!  objects_nav_tree(+Objects, -Tree) is det.
 1303%
 1304%   Provide a navigation tree showing the context of Object.  Tree
 1305%   is of the form node(Object, Children).
 1306
 1307objects_nav_tree(Objects, Tree) :-
 1308    maplist(object_nav_tree, Objects, Trees),
 1309    union_trees(Trees, Tree0),
 1310    remove_unique_root(Tree0, Tree).
 1311
 1312object_nav_tree(Obj, Tree) :-
 1313    Node = node(directory(Dir), FileNodes),
 1314    FileNode = node(file(File), Siblings),
 1315    doc_comment(Obj, File:_Line, _Summary, _Comment),
 1316    !,
 1317    file_directory_name(File, Dir),
 1318    sibling_file_nodes(Dir, FileNodes0),
 1319    selectchk(node(file(File),[]), FileNodes0, FileNode, FileNodes),
 1320    findall(Sibling, doc_comment(Sibling, File:_, _, _), Siblings0),
 1321    delete(Siblings0, _:module(_), Siblings1),
 1322    doc_hide_private(Siblings1, Siblings2, []),
 1323    flatten(Siblings2, Siblings),   % a comment may describe objects
 1324    embed_directories(Node, Tree).
 1325
 1326sibling_file_nodes(Dir, Nodes) :-
 1327    findall(node(file(File), []),
 1328	    (   source_file(File),
 1329		file_directory_name(File, Dir)
 1330	    ),
 1331	    Nodes).
 1332
 1333embed_directories(Node, Tree) :-
 1334    Node = node(file(File), _),
 1335    !,
 1336    file_directory_name(File, Dir),
 1337    Super = node(directory(Dir), [Node]),
 1338    embed_directories(Super, Tree).
 1339embed_directories(Node, Tree) :-
 1340    Node = node(directory(Dir), _),
 1341    file_directory_name(Dir, SuperDir),
 1342    SuperDir \== Dir,
 1343    !,
 1344    Super = node(directory(SuperDir), [Node]),
 1345    embed_directories(Super, Tree).
 1346embed_directories(Tree, Tree).
 1347
 1348
 1349union_trees([Tree], Tree) :- !.
 1350union_trees([T1,T2|Trees], Tree) :-
 1351    merge_trees(T1, T2, M1),
 1352    union_trees([M1|Trees], Tree).
 1353
 1354merge_trees(node(R, Ch1), node(R, Ch2), node(R, Ch)) :-
 1355    merge_nodes(Ch1, Ch2, Ch).
 1356
 1357merge_nodes([], Ch, Ch) :- !.
 1358merge_nodes(Ch, [], Ch) :- !.
 1359merge_nodes([node(Root, Ch1)|T1], N1, [T1|Nodes]) :-
 1360    selectchk(node(Root, Ch2), N1, N2),
 1361    !,
 1362    merge_trees(node(Root, Ch1), node(Root, Ch2), T1),
 1363    merge_nodes(T1, N2, Nodes).
 1364merge_nodes([Node|T1], N1, [Node|Nodes]) :-
 1365    merge_nodes(T1, N1, Nodes).
 1366
 1367%!  remove_unique_root(+TreeIn, -Tree)
 1368%
 1369%   Remove the root part that does not branch
 1370
 1371remove_unique_root(node(_, [node(R1, [R2])]), Tree) :-
 1372    !,
 1373    remove_unique_root(node(R1, [R2]), Tree).
 1374remove_unique_root(Tree, Tree).
 1375
 1376%!  nav_tree(+Tree, +Current, +Options)// is det.
 1377%
 1378%   Render the navigation tree
 1379
 1380nav_tree(Tree, Current, Options) -->
 1381    html(ul(class(nav),
 1382	    \object_tree(Tree, Current, Options))).
 1383
 1384%!  object_tree(+Tree, +Current, +Options)// is det.
 1385%
 1386%   Render a tree of objects used for navigation.
 1387
 1388object_tree(node(Id, []), Target, Options) -->
 1389    !,
 1390    { node_class(Id, Target, Class) },
 1391    html(li(class(Class),
 1392	    \node(Id, Options))).
 1393object_tree(node(Id, Children), Target, Options) -->
 1394    !,
 1395    { node_class(Id, Target, Class) },
 1396    html(li(class(Class),
 1397	    [ \node(Id, Options),
 1398	      ul(class(nav),
 1399		 \object_trees(Children, Target, Options))
 1400	    ])).
 1401object_tree(Id, Target, Options) -->
 1402    !,
 1403    { node_class(Id, Target, Class) },
 1404    html(li(class([obj|Class]), \node(Id, Options))).
 1405
 1406object_trees([], _, _) --> [].
 1407object_trees([H|T], Target, Options) -->
 1408    object_tree(H, Target, Options),
 1409    object_trees(T, Target, Options).
 1410
 1411node_class(Ids, Current, Class) :-
 1412    is_list(Ids),
 1413    !,
 1414    (   member(Id, Ids), memberchk(Id, Current)
 1415    ->  Class = [nav,current]
 1416    ;   Class = [nav]
 1417    ).
 1418node_class(Id, Current, Class) :-
 1419    (   memberchk(Id, Current)
 1420    ->  Class = [nav,current]
 1421    ;   Class = [nav]
 1422    ).
 1423
 1424node(file(File), Options) -->
 1425    !,
 1426    object_ref(file(File), [style(title)|Options]).
 1427node(Id, Options) -->
 1428    object_ref(Id, Options).
 1429
 1430
 1431		 /*******************************
 1432		 *            SECTIONS          *
 1433		 *******************************/
 1434
 1435section(Type, Title) -->
 1436    { string_codes(Title, Codes),
 1437      wiki_codes_to_dom(Codes, [], Content0),
 1438      strip_leading_par(Content0, Content),
 1439      make_section(Type, Content, HTML)
 1440    },
 1441    html(HTML).
 1442
 1443make_section(module,  Title, h1(class=module,  Title)).
 1444make_section(section, Title, h1(class=section, Title)).
 1445
 1446
 1447		 /*******************************
 1448		 *       PRED MODE HEADER       *
 1449		 *******************************/
 1450
 1451%!  pred_dt(+Modes, +Class, Options)// is det.
 1452%
 1453%   Emit the predicate header.
 1454%
 1455%   @param Modes    List as returned by process_modes/5.
 1456
 1457pred_dt(Modes, Class, Options) -->
 1458    pred_dt(Modes, Class, [], _Done, Options).
 1459
 1460pred_dt([], _, Done, Done, _) -->
 1461    [].
 1462pred_dt([H|T], Class, Done0, Done, Options) -->
 1463    { functor(Class, CSSClass, _) },
 1464    html(dt(class=CSSClass,
 1465	    [ \pred_mode(H, Done0, Done1, Options),
 1466	      \mode_anot(Class)
 1467	    ])),
 1468    pred_dt(T, Class, Done1, Done, Options).
 1469
 1470mode_anot(privdef) -->
 1471    !,
 1472    html(span([class(anot), style('float:right')],
 1473	      '[private]')).
 1474mode_anot(multidef(object(Obj))) -->
 1475    !,
 1476    { object_href(Obj, HREF) },
 1477    html(span([class(anot), style('float:right')],
 1478	      ['[', a(href(HREF), multifile), ']'
 1479	      ])).
 1480mode_anot(multidef(file(File:_))) -->
 1481    !,
 1482    { file_name_on_path(File, Spec),
 1483      unquote_filespec(Spec, Unquoted),
 1484      doc_file_href(File, HREF)
 1485    },
 1486    html(span([class(anot), style('float:right')],
 1487	      ['[multifile, ', a(href(HREF), '~q'-[Unquoted]), ']'
 1488	      ])).
 1489mode_anot(multidef) -->
 1490    !,
 1491    html(span([class(anot), style('float:right')],
 1492	      '[multifile]')).
 1493mode_anot(_) -->
 1494    [].
 1495
 1496pred_mode(mode(Head,Vars), Done0, Done, Options) -->
 1497    !,
 1498    { bind_vars(Head, Vars) },
 1499    pred_mode(Head, Done0, Done, Options).
 1500pred_mode(Head is Det, Done0, Done, Options) -->
 1501    !,
 1502    anchored_pred_head(Head, Done0, Done, Options),
 1503    pred_det(Det).
 1504pred_mode(Head, Done0, Done, Options) -->
 1505    anchored_pred_head(Head, Done0, Done, Options).
 1506
 1507bind_vars(Term, Bindings) :-
 1508    bind_vars(Bindings),
 1509    anon_vars(Term).
 1510
 1511bind_vars([]).
 1512bind_vars([Name=Var|T]) :-
 1513    Var = '$VAR'(Name),
 1514    bind_vars(T).
 1515
 1516%!  anon_vars(+Term) is det.
 1517%
 1518%   Bind remaining variables in Term to '$VAR'('_'), so they are
 1519%   printed as '_'.
 1520
 1521anon_vars(Var) :-
 1522    var(Var),
 1523    !,
 1524    Var = '$VAR'('_').
 1525anon_vars(Term) :-
 1526    compound(Term),
 1527    !,
 1528    Term =.. [_|Args],
 1529    maplist(anon_vars, Args).
 1530anon_vars(_).
 1531
 1532
 1533anchored_pred_head(Head, Done0, Done, Options) -->
 1534    { pred_anchor_name(Head, PI, Name) },
 1535    (   { memberchk(PI, Done0) }
 1536    ->  { Done = Done0 },
 1537	pred_head(Head)
 1538    ;   html([ span(style('float:right'),
 1539		    [ \pred_edit_or_source_button(Head, Options),
 1540		      &(nbsp)
 1541		    ]),
 1542	       a(name=Name, \pred_head(Head))
 1543	     ]),
 1544	{ Done = [PI|Done0] }
 1545    ).
 1546
 1547
 1548pred_edit_or_source_button(Head, Options) -->
 1549    { option(edit(true), Options) },
 1550    !,
 1551    pred_edit_button(Head, Options).
 1552pred_edit_or_source_button(Head, Options) -->
 1553    { option(source_link(true), Options) },
 1554    !,
 1555    pred_source_button(Head, Options).
 1556pred_edit_or_source_button(_, _) --> [].
 1557
 1558%!  pred_edit_button(+PredIndicator, +Options)// is det.
 1559%
 1560%   Create a button for editing the given predicate.  Options
 1561%   processed:
 1562%
 1563%       * module(M)
 1564%       Resolve to module M
 1565%       * file(F)
 1566%       For multi-file predicates: link to version in file.
 1567%       * line(L)
 1568%       Line to edit (in file)
 1569
 1570pred_edit_button(_, Options) -->
 1571    { \+ option(edit(true), Options) },
 1572    !.
 1573pred_edit_button(PI0, Options0) -->
 1574    { canonicalise_predref(PI0, PI, Options0, Options) },
 1575    pred_edit_button2(PI, Options).
 1576
 1577pred_edit_button2(Name/Arity, Options) -->
 1578    { \+ ( memberchk(file(_), Options), % always edit if file and line
 1579	   memberchk(line(_), Options)  % are given.
 1580	 ),
 1581      functor(Head, Name, Arity),
 1582      option(module(M), Options, _),
 1583      \+ ( current_module(M),
 1584	   source_file(M:Head, _File)
 1585	 )
 1586    },
 1587    !.
 1588pred_edit_button2(Name/Arity, Options) -->
 1589    { include(edit_param, Options, Extra),
 1590      http_link_to_id(pldoc_edit,
 1591		      [name(Name),arity(Arity)|Extra],
 1592		      EditHREF)
 1593    },
 1594    html(a(onClick('HTTPrequest(\'' + EditHREF + '\')'),
 1595	   img([ class(action),
 1596		 alt('Edit predicate'),
 1597		 title('Edit predicate'),
 1598		 src(location_by_id(pldoc_resource)+'editpred.png')
 1599	       ]))).
 1600pred_edit_button2(_, _) -->
 1601    !,
 1602    [].
 1603
 1604edit_param(module(_)).
 1605edit_param(file(_)).
 1606edit_param(line(_)).
 1607
 1608
 1609%!  object_edit_button(+Object, +Options)// is det.
 1610%
 1611%   Create a button for editing Object.
 1612
 1613object_edit_button(_, Options) -->
 1614    { \+ option(edit(true), Options) },
 1615    !.
 1616object_edit_button(PI, Options) -->
 1617    { is_pi(PI) },
 1618    !,
 1619    pred_edit_button(PI, Options).
 1620object_edit_button(_, _) -->
 1621    [].
 1622
 1623
 1624%!  pred_source_button(+PredIndicator, +Options)// is det.
 1625%
 1626%   Create a button for viewing the source of a predicate.
 1627
 1628pred_source_button(PI0, Options0) -->
 1629    { canonicalise_predref(PI0, PI, Options0, Options),
 1630      option(module(M), Options, _),
 1631      pred_source_href(PI, M, HREF), !
 1632    },
 1633    html(a([ href(HREF)
 1634	   ],
 1635	   img([ class(action),
 1636		 alt('Source'),
 1637		 title('Show source'),
 1638		 src(location_by_id(pldoc_resource)+'source.png')
 1639	       ]))).
 1640pred_source_button(_, _) -->
 1641    [].
 1642
 1643
 1644%!  object_source_button(+Object, +Options)// is det.
 1645%
 1646%   Create a button for showing the source of Object.
 1647
 1648object_source_button(PI, Options) -->
 1649    { is_pi(PI),
 1650      option(source_link(true), Options, true)
 1651    },
 1652    !,
 1653    pred_source_button(PI, Options).
 1654object_source_button(_, _) -->
 1655    [].
 1656
 1657
 1658%!  canonicalise_predref(+PredRef, -PI:Name/Arity, +Options0, -Options) is det.
 1659%
 1660%   Canonicalise a predicate reference. A   possible module qualifier is
 1661%   added as module(M) to Options.
 1662
 1663canonicalise_predref(M:PI0, PI, Options0, [module(M)|Options]) :-
 1664    !,
 1665    canonicalise_predref(PI0, PI, Options0, Options).
 1666canonicalise_predref(//(Head), PI, Options0, Options) :-
 1667    !,
 1668    functor(Head, Name, Arity),
 1669    PredArity is Arity + 2,
 1670    canonicalise_predref(Name/PredArity, PI, Options0, Options).
 1671canonicalise_predref(Name//Arity, PI, Options0, Options) :-
 1672    integer(Arity), Arity >= 0,
 1673    !,
 1674    PredArity is Arity + 2,
 1675    canonicalise_predref(Name/PredArity, PI, Options0, Options).
 1676canonicalise_predref(PI, PI, Options, Options) :-
 1677    PI = Name/Arity,
 1678    atom(Name), integer(Arity), Arity >= 0,
 1679    !.
 1680canonicalise_predref(Head, PI, Options0, Options) :-
 1681    functor(Head, Name, Arity),
 1682    canonicalise_predref(Name/Arity, PI, Options0, Options).
 1683
 1684
 1685%!  pred_head(+Term) is det.
 1686%
 1687%   Emit a predicate head. The functor is  typeset as a =span= using
 1688%   class =pred= and the arguments and =var= using class =arglist=.
 1689
 1690pred_head(Var) -->
 1691    { var(Var),
 1692      !,
 1693      instantiation_error(Var)
 1694    }.
 1695pred_head(//(Head)) -->
 1696    !,
 1697    pred_head(Head),
 1698    html(//).
 1699pred_head(M:Head) -->
 1700    html([span(class=module, M), :]),
 1701    pred_head(Head).
 1702pred_head(Head) -->
 1703    { atom(Head) },
 1704    !,
 1705    html(b(class=pred, Head)).
 1706pred_head(Head) -->                     % Infix operators
 1707    { Head =.. [Functor,Left,Right],
 1708      is_op_type(Functor, infix)
 1709    },
 1710    !,
 1711    html([ var(class=arglist, \pred_arg(Left, 1)),
 1712	   ' ', b(class=pred, Functor), ' ',
 1713	   var(class=arglist, \pred_arg(Right, 2))
 1714	 ]).
 1715pred_head(Head) -->                     % Prefix operators
 1716    { Head =.. [Functor,Arg],
 1717      is_op_type(Functor, prefix)
 1718    },
 1719    !,
 1720    html([ b(class=pred, Functor), ' ',
 1721	   var(class=arglist, \pred_arg(Arg, 1))
 1722	 ]).
 1723pred_head(Head) -->                     % Postfix operators
 1724    { Head =.. [Functor,Arg],
 1725      is_op_type(Functor, postfix)
 1726    },
 1727    !,
 1728    html([ var(class=arglist, \pred_arg(Arg, 1)),
 1729	   ' ', b(class=pred, Functor)
 1730	 ]).
 1731pred_head({Head}) -->
 1732    !,
 1733    html([ b(class=pred, '{'),
 1734	   var(class=arglist,
 1735	       \pred_args([Head], 1)),
 1736	   b(class=pred, '}')
 1737	 ]).
 1738pred_head(Head) -->                     % Plain terms
 1739    { Head =.. [Functor|Args] },
 1740    html([ b(class=pred, Functor),
 1741	   var(class=arglist,
 1742	       [ '(', \pred_args(Args, 1), ')' ])
 1743	 ]).
 1744
 1745%!  is_op_type(+Atom, ?Type)
 1746%
 1747%   True if Atom is an operator of   Type.  Type is one of =prefix=,
 1748%   =infix= or =postfix=.
 1749
 1750is_op_type(Functor, Type) :-
 1751    current_op(_Pri, F, Functor),
 1752    op_type(F, Type).
 1753
 1754op_type(fx,  prefix).
 1755op_type(fy,  prefix).
 1756op_type(xf,  postfix).
 1757op_type(yf,  postfix).
 1758op_type(xfx, infix).
 1759op_type(xfy, infix).
 1760op_type(yfx, infix).
 1761op_type(yfy, infix).
 1762
 1763
 1764pred_args([], _) -->
 1765    [].
 1766pred_args([H|T], I) -->
 1767    pred_arg(H, I),
 1768    (   {T==[]}
 1769    ->  []
 1770    ;   html(', '),
 1771	{ I2 is I + 1 },
 1772	pred_args(T, I2)
 1773    ).
 1774
 1775pred_arg(Var, I) -->
 1776    { var(Var) },
 1777    !,
 1778    html(['Arg', I]).
 1779pred_arg(...(Term), I) -->
 1780    !,
 1781    pred_arg(Term, I),
 1782    html('...').
 1783pred_arg(Term, I) -->
 1784    { Term =.. [Ind,Arg],
 1785      mode_indicator(Ind)
 1786    },
 1787    !,
 1788    html([Ind, \pred_arg(Arg, I)]).
 1789pred_arg(Arg:Type, _) -->
 1790    !,
 1791    html([\argname(Arg), :, \argtype(Type)]).
 1792pred_arg(Arg, _) -->
 1793    argname(Arg).
 1794
 1795argname('$VAR'(Name)) -->
 1796    !,
 1797    html(Name).
 1798argname(Name) -->
 1799    !,
 1800    html(Name).
 1801
 1802argtype(Term) -->
 1803    { format(string(S), '~W',
 1804	     [ Term,
 1805	       [ quoted(true),
 1806		 numbervars(true)
 1807	       ]
 1808	     ]) },
 1809    html(S).
 1810
 1811pred_det(unknown) -->
 1812    [].
 1813pred_det(Det) -->
 1814    html([' is ', b(class=det, Det)]).
 1815
 1816
 1817%!  term(+Text, +Term, +Bindings)// is det.
 1818%
 1819%   Process the \term element as produced by doc_wiki.pl.
 1820%
 1821%   @tbd    Properly merge with pred_head//1
 1822
 1823term(_, Atom, []) -->
 1824    { atomic(Atom),
 1825      !,
 1826      format(string(S), '~W', [Atom,[quoted(true)]])
 1827    },
 1828    html(span(class=functor, S)).
 1829term(_, Key:Type, [TypeName=Type]) -->
 1830    { atomic(Key)
 1831    },
 1832    !,
 1833    html([span(class='pl-key', Key), :, span(class('pl-var'), TypeName)]).
 1834term(_, Term, Bindings) -->
 1835    { is_mode(Term is det),         % HACK. Bit too strict?
 1836      bind_vars(Bindings)
 1837    },
 1838    !,
 1839    pred_head(Term).
 1840term(_, Term, Bindings) -->
 1841    term(Term,
 1842	 [ variable_names(Bindings),
 1843	   quoued(true)
 1844	 ]).
 1845
 1846
 1847		 /*******************************
 1848		 *             PREDREF          *
 1849		 *******************************/
 1850
 1851%!  predref(+PI)// is det.
 1852%!  predref(+PI, +Options)// is det.
 1853%
 1854%   Create a reference to a predicate. The reference consists of the
 1855%   relative path to the  file  using   the  predicate  indicator as
 1856%   anchor.
 1857%
 1858%   Current file must  be  available   through  the  global variable
 1859%   =pldoc_file=. If this variable not  set   it  creates  a link to
 1860%   /doc/<file>#anchor.  Such links only work in the online browser.
 1861
 1862predref(Term) -->
 1863    { catch(nb_getval(pldoc_options, Options), _, Options = []) },
 1864    predref(Term, Options).
 1865
 1866predref(Obj, Options) -->
 1867    { Obj = _:_,
 1868      doc_comment(Obj, File:_Line, _, _),
 1869      (   (   option(files(Map), Options)
 1870	  ->  memberchk(file(File,_), Map)
 1871	  ;   true
 1872	  )
 1873      ->  object_href(Obj, HREF, Options)
 1874      ;   manref(Obj, HREF, Options)
 1875      )
 1876    },
 1877    !,
 1878    html(a(href(HREF), \object_name(Obj, [qualify(true)|Options]))).
 1879predref(M:Term, Options) -->
 1880    !,
 1881    predref(Term, M, Options).
 1882predref(Term, Options) -->
 1883    predref(Term, _, Options).
 1884
 1885predref(Name/Arity, _, Options) -->             % Builtin; cannot be overruled
 1886    { prolog:doc_object_summary(Name/Arity, manual, _, _),
 1887      !,
 1888      manref(Name/Arity, HREF, Options)
 1889    },
 1890    html(a([class=builtin, href=HREF], [Name, /, Arity])).
 1891predref(Name/Arity, _, Options) -->             % From packages
 1892    { option(prefer(manual), Options),
 1893      prolog:doc_object_summary(Name/Arity, Category, _, _),
 1894      !,
 1895      manref(Name/Arity, HREF, Options)
 1896    },
 1897    html(a([class=Category, href=HREF], [Name, /, Arity])).
 1898predref(Obj, Module, Options) -->               % Local
 1899    { doc_comment(Module:Obj, File:_Line, _, _),
 1900      (   option(files(Map), Options)
 1901      ->  memberchk(file(File,_), Map)
 1902      ;   true
 1903      )
 1904    },
 1905    !,
 1906    object_ref(Module:Obj, Options).
 1907predref(Name/Arity, Module, Options) -->
 1908    { \+ option(files(_), Options),
 1909      pred_href(Name/Arity, Module, HREF)
 1910    },
 1911    !,
 1912    html(a(href=HREF, [Name, /, Arity])).
 1913predref(Name//Arity, Module, Options) -->
 1914    { \+ option(files(_), Options),
 1915      PredArity is Arity + 2,
 1916      pred_href(Name/PredArity, Module, HREF)
 1917    },
 1918    !,
 1919    html(a(href=HREF, [Name, //, Arity])).
 1920predref(PI, _, Options) -->             % From packages
 1921    { canonical_pi(PI, CPI, HTML),
 1922      (   option(files(_), Options)
 1923      ->  Category = extmanual
 1924      ;   prolog:doc_object_summary(CPI, Category, _, _)
 1925      ),
 1926      manref(CPI, HREF, Options)
 1927    },
 1928    html(a([class=Category, href=HREF], HTML)).
 1929predref(PI, _, _Options) -->
 1930    { canonical_pi(PI, _CPI, HTML)
 1931    },
 1932    !,
 1933    html(span(class=undef, HTML)).
 1934predref(Callable, Module, Options) -->
 1935    { callable(Callable),
 1936      functor(Callable, Name, Arity)
 1937    },
 1938    predref(Name/Arity, Module, Options).
 1939
 1940canonical_pi(Name/Arity, Name/Arity, [Name, /, Arity]) :-
 1941    atom(Name), integer(Arity),
 1942    !.
 1943canonical_pi(Name//Arity, Name/Arity2, [Name, //, Arity]) :-
 1944    atom(Name), integer(Arity),
 1945    !,
 1946    Arity2 is Arity+2.
 1947
 1948%!  nopredref(+PI)//
 1949%
 1950%   Result of ``name/arity``, non-linking predicate indicator.
 1951
 1952nopredref(PI) -->
 1953    { canonical_pi(PI, _CPI, HTML)
 1954    },
 1955    !,
 1956    html(span(class=nopredref, HTML)).
 1957
 1958%!  flagref(+Flag)//
 1959%
 1960%   Reference to a Prolog flag.
 1961%
 1962%   @tbd generate a link to the Prolog website?
 1963
 1964flagref(Flag) -->
 1965    html(code(Flag)).
 1966
 1967%!  cite(+Citations)// is det.
 1968%
 1969%   Emit citations. This is indented to   allow  for [@cite1;@cite2] for
 1970%   generating LaTex.
 1971
 1972cite(Citations) -->
 1973    html('['), citations(Citations), html(']').
 1974
 1975citations([]) --> [].
 1976citations([H|T]) -->
 1977    citation(H),
 1978    (   {T==[]}
 1979    ->  []
 1980    ;   [';'],
 1981	citations(T)
 1982    ).
 1983
 1984citation(H) -->
 1985    html([@,H]).
 1986
 1987
 1988%!  manref(+NameArity, -HREF, +Options) is det.
 1989%
 1990%   Create reference to a manual page.  When generating files, this
 1991%   listens to the option man_server(+Server).
 1992
 1993manref(PI, HREF, Options) :-
 1994    predname(PI, PredName),
 1995    (   option(files(_Map), Options)
 1996    ->  option(man_server(Server), Options,
 1997	       'http://www.swi-prolog.org/pldoc'),
 1998	uri_components(Server, Comp0),
 1999	uri_data(path, Comp0, Path0),
 2000	directory_file_path(Path0, man, Path),
 2001	uri_data(path, Comp0, Path, Components),
 2002	uri_query_components(Query, [predicate=PredName]),
 2003	uri_data(search, Components, Query),
 2004	uri_components(HREF, Components)
 2005    ;   http_link_to_id(pldoc_man, [predicate=PredName], HREF)
 2006    ).
 2007
 2008predname(Name/Arity, PredName) :-
 2009    !,
 2010    format(atom(PredName), '~w/~d', [Name, Arity]).
 2011predname(Module:Name/Arity, PredName) :-
 2012    !,
 2013    format(atom(PredName), '~w:~w/~d', [Module, Name, Arity]).
 2014
 2015
 2016%!  pred_href(+NameArity, +Module, -HREF) is semidet.
 2017%
 2018%   Create reference.  Prefer:
 2019%
 2020%           1. Local definition
 2021%           2. If from package and documented: package documentation
 2022%           3. From any file
 2023%
 2024%   @bug    Should analyse import list to find where the predicate
 2025%           comes from.
 2026
 2027pred_href(Name/Arity, Module, HREF) :-
 2028    format(string(FragmentId), '~w/~d', [Name, Arity]),
 2029    uri_data(fragment, Components, FragmentId),
 2030    functor(Head, Name, Arity),
 2031    (   catch(relative_file(Module:Head, File), _, fail)
 2032    ->  uri_data(path, Components, File),
 2033	uri_components(HREF, Components)
 2034    ;   in_file(Module:Head, File)
 2035    ->  (   current_prolog_flag(home, SWI),
 2036	    sub_atom(File, 0, _, _, SWI),
 2037	    prolog:doc_object_summary(Name/Arity, packages, _, _)
 2038	->  http_link_to_id(pldoc_man, [predicate=FragmentId], HREF)
 2039	;   http_location_by_id(pldoc_doc, DocHandler),
 2040	    atom_concat(DocHandler, File, Path),
 2041	    uri_data(path, Components, Path),
 2042	    uri_components(HREF, Components)
 2043	)
 2044    ).
 2045
 2046relative_file(Head, '') :-
 2047    b_getval(pldoc_file, CurrentFile), CurrentFile \== [],
 2048    in_file(Head, CurrentFile),
 2049    !.
 2050relative_file(Head, RelFile) :-
 2051    b_getval(pldoc_file, CurrentFile), CurrentFile \== [],
 2052    in_file(Head, DefFile),
 2053    relative_file_name(DefFile, CurrentFile, RelFile).
 2054
 2055%!  pred_source_href(+Pred:predicate_indicator, +Module, -HREF) is semidet.
 2056%
 2057%   HREF is a URL to show the predicate source in its file.
 2058
 2059pred_source_href(Name/Arity, Module, HREF) :-
 2060    format(string(FragmentId), '~w/~d', [Name, Arity]),
 2061    uri_data(fragment, Components, FragmentId),
 2062    uri_query_components(Query, [show=src]),
 2063    uri_data(search, Components, Query),
 2064    functor(Head, Name, Arity),
 2065    (   catch(relative_file(Module:Head, File), _, fail)
 2066    ->  uri_data(path, Components, File),
 2067	uri_components(HREF, Components)
 2068    ;   in_file(Module:Head, File0)
 2069    ->  insert_alias(File0, File),
 2070	http_location_by_id(pldoc_doc, DocHandler),
 2071	atom_concat(DocHandler, File, Path),
 2072	uri_data(path, Components, Path),
 2073	uri_components(HREF, Components)
 2074    ).
 2075
 2076
 2077%!  object_ref(+Object, +Options)// is det.
 2078%
 2079%   Create a hyperlink to Object. Points to the /doc_for URL. Object
 2080%   is as the first argument of doc_comment/4.   Note  this can be a
 2081%   list of objects.
 2082
 2083object_ref([], _) -->
 2084    !,
 2085    [].
 2086object_ref([H|T], Options) -->
 2087    !,
 2088    object_ref(H, Options),
 2089    (   {T == []}
 2090    ->  html(', '),
 2091	object_ref(T, Options)
 2092    ;   []
 2093    ).
 2094object_ref(Obj, Options) -->
 2095    { object_href(Obj, HREF, Options)
 2096    },
 2097    html(a(href(HREF), \object_name(Obj, Options))).
 2098
 2099%!  object_href(+Object, -HREF) is det.
 2100%!  object_href(+Object, -HREF, +Options) is det.
 2101%
 2102%   HREF is the URL to access Object.
 2103
 2104object_href(Obj, HREF) :-
 2105    object_href(Obj, HREF, []).
 2106
 2107object_href(M:PI0, HREF, Options) :-
 2108    option(files(Map), Options),
 2109    (   module_property(M, file(File))
 2110    ->  true
 2111    ;   xref_module(File, M)
 2112    ),
 2113    memberchk(file(File, DocFile), Map),
 2114    !,
 2115    file_base_name(DocFile, LocalFile),     % TBD: proper directory index
 2116    expand_pi(PI0, PI),
 2117    term_to_string(PI, PIS),
 2118    uri_data(path, Components, LocalFile),
 2119    uri_data(fragment, Components, PIS),
 2120    uri_components(HREF, Components).
 2121object_href(file(File), HREF, _Options) :-
 2122    doc_file_href(File, HREF),
 2123    !.
 2124object_href(directory(Dir), HREF, _Options) :-
 2125    directory_file_path(Dir, 'index.html', Index),
 2126    doc_file_href(Index, HREF),
 2127    !.
 2128object_href(Obj, HREF, _Options) :-
 2129    prolog:doc_object_href(Obj, HREF),
 2130    !.
 2131object_href(Obj0, HREF, _Options) :-
 2132    localise_object(Obj0, Obj),
 2133    term_to_string(Obj, String),
 2134    http_link_to_id(pldoc_object, [object=String], HREF).
 2135
 2136expand_pi(Name//Arity0, Name/Arity) :-
 2137    !,
 2138    Arity is Arity0+2.
 2139expand_pi(PI, PI).
 2140
 2141
 2142%!  localise_object(+ObjIn, -ObjOut) is det.
 2143%
 2144%   Abstract  path-details  to  make  references  more  stable  over
 2145%   versions.
 2146
 2147localise_object(Obj0, Obj) :-
 2148    prolog:doc_canonical_object(Obj0, Obj),
 2149    !.
 2150localise_object(Obj, Obj).
 2151
 2152
 2153%!  term_to_string(+Term, -String) is det.
 2154%
 2155%   Convert Term, possibly  holding  variables,   into  a  canonical
 2156%   string using A, B, ... for variables and _ for singletons.
 2157
 2158term_to_string(Term, String) :-
 2159    State = state(-),
 2160    (   numbervars(Term, 0, _, [singletons(true)]),
 2161	with_output_to(string(String),
 2162		       write_term(Term,
 2163				  [ numbervars(true),
 2164				    quoted(true)
 2165				  ])),
 2166	nb_setarg(1, State, String),
 2167	fail
 2168    ;   arg(1, State, String)
 2169    ).
 2170
 2171%!  object_name(+Obj, +Options)// is det.
 2172%
 2173%   HTML description of documented Obj. Obj is as the first argument
 2174%   of doc_comment/4.  Options:
 2175%
 2176%     - style(+Style)
 2177%     One of =inline= or =title=
 2178%     - qualify(+Boolean)
 2179%     Qualify predicates by their module
 2180%     - secref_style(Style)
 2181%     One of =number=, =title= or =number_title=
 2182
 2183object_name(Obj, Options) -->
 2184    { option(style(Style), Options, inline)
 2185    },
 2186    object_name(Style, Obj, Options).
 2187
 2188object_name(title, Obj, Options) -->
 2189    { merge_options(Options, [secref_style(title)], Options1) },
 2190    prolog:doc_object_link(Obj, Options1),
 2191    !.
 2192object_name(inline, Obj, Options) -->
 2193    prolog:doc_object_link(Obj, Options),
 2194    !.
 2195object_name(title, f(Name/Arity), _Options) -->
 2196    !,
 2197    html(['Function ', Name, /, Arity]).
 2198object_name(inline, f(Name/Arity), _Options) -->
 2199    !,
 2200    html([Name, /, Arity]).
 2201object_name(Style, PI, Options) -->
 2202    { is_pi(PI) },
 2203    !,
 2204    pi(Style, PI, Options).
 2205object_name(inline, Module:module(_Title), _) -->
 2206    !,
 2207    { module_property(Module, file(File)),
 2208      file_base_name(File, Base)
 2209    },
 2210    !,
 2211    html(Base).
 2212object_name(title, Module:module(Title), _) -->
 2213    { module_property(Module, file(File)),
 2214      file_base_name(File, Base)
 2215    },
 2216    !,
 2217    html([Base, ' -- ', Title]).
 2218object_name(title, file(File), _) -->
 2219    { module_property(Module, file(File)),
 2220      doc_comment(Module:module(Title), _, _, _),
 2221      !,
 2222      file_base_name(File, Base)
 2223    },
 2224    html([Base, ' -- ', Title]).
 2225object_name(_, file(File), _) -->
 2226    { file_base_name(File, Base) },
 2227    html(Base).
 2228object_name(_, directory(Dir), _) -->
 2229    { file_base_name(Dir, Base) },
 2230    html(Base).
 2231object_name(_, module(Title), _Options) -->
 2232    { print_message(warning,
 2233		    pldoc(module_comment_outside_module(Title)))
 2234    }.
 2235
 2236pi(title, PI, Options) -->
 2237    pi_type(PI),
 2238    pi(PI, Options).
 2239pi(inline, PI, Options) -->
 2240    pi(PI, Options).
 2241
 2242pi(M:PI, Options) -->
 2243    !,
 2244    (   { option(qualify(true), Options) }
 2245    ->  html([span(class(module), M), :])
 2246    ;   []
 2247    ),
 2248    pi(PI, Options).
 2249pi(Name/Arity, _) -->
 2250    !,
 2251    html([Name, /, Arity]).
 2252pi(Name//Arity, _) -->
 2253    html([Name, //, Arity]).
 2254
 2255pi_type(_:PI) -->
 2256    !,
 2257    pi_type(PI).
 2258pi_type(_/_) -->
 2259    html(['Predicate ']).
 2260pi_type(_//_) -->
 2261    html(['Grammar rule ']).
 2262
 2263
 2264
 2265%!  in_file(+Head, ?File) is nondet.
 2266%
 2267%   File is the name of a file containing the Predicate Head.
 2268%   Head may be qualified with a module.
 2269%
 2270%   @tbd Prefer local, then imported, then `just anywhere'
 2271%   @tbd Look for documented and/or public predicates.
 2272
 2273in_file(Module:Head, File) :-
 2274    !,
 2275    distinct(File, in_file(Module, Head, File)).
 2276in_file(Head, File) :-
 2277    distinct(File, in_file(_, Head, File)).
 2278
 2279in_file(Module, Head, File) :-
 2280    var(Module),
 2281    (   predicate_property(system:Head, foreign)
 2282    ->  !,
 2283	fail
 2284    ;   predicate_property(system:Head, file(File)),
 2285	\+ system_arithmetic_function(Head)
 2286    ->  !
 2287    ;   predicate_property(Head, autoload(File0))
 2288    ->  !,
 2289	file_name_extension(File0, pl, File)
 2290    ;   exported_from(Module, Head, File),
 2291	module_property(Module, class(library))
 2292    ).
 2293in_file(Module, Head, File) :-
 2294    nonvar(Module),
 2295    predicate_property(Module:Head, file(File)),
 2296    \+ predicate_property(Module:Head, imported_from(_)).
 2297in_file(Module, Head, File) :-
 2298    xref_defined(File, Head, How),
 2299    xref_current_source(File),
 2300    atom(File),                     % only plain files
 2301    xref_module(File, Module),
 2302    How \= imported(_From).
 2303in_file(Module, Head, File) :-
 2304    exported_from(Module, Head, File).
 2305in_file(Module, Head, File) :-
 2306    predicate_property(Module:Head, file(File)),
 2307    \+ predicate_property(Module:Head, imported_from(_)).
 2308in_file(Module, Head, File) :-
 2309    current_module(Module),
 2310    source_file(Module:Head, File).
 2311
 2312exported_from(Module, Head, File) :-
 2313    distinct(Primary,
 2314	     (   predicate_property(Module:Head, exported),
 2315		 (   predicate_property(Module:Head, imported_from(Primary))
 2316		 ->  true
 2317		 ;   Primary = Module
 2318		 ))),
 2319    module_property(Primary, file(File)).
 2320
 2321:- multifile
 2322    arithmetic:evaluable/2. 2323
 2324system_arithmetic_function(Head) :-
 2325    functor(Head, Name, Arity),
 2326    FArith is Arity-1,
 2327    FArith >= 0,
 2328    functor(FHead, Name, FArith),
 2329    arithmetic:evaluable(FHead, system).
 2330
 2331%%     file(+FileName)// is det.
 2332%%     file(+FileName, +Options)// is det.
 2333%
 2334%      Create a link to another filename if   the file exists. Called by
 2335%      \file(File) terms in the DOM term generated by wiki.pl. Supported
 2336%      options are:
 2337%
 2338%          * label(+Label)
 2339%          Label to use for the link to the file.
 2340%
 2341%          * absolute_path(+Path)
 2342%          Absolute location of the referenced file.
 2343%
 2344%          * href(+HREF)
 2345%          Explicitely provided link; overrule link computation.
 2346%
 2347%          * map_extension(+Pairs)
 2348%          Map the final extension if OldExt-NewExt is in Pairs.
 2349%
 2350%          * files(+Map)
 2351%          List of file(Name, Link) that specifies that we must
 2352%          user Link for the given physical file Name.
 2353%
 2354%          * edit_handler(+Id)
 2355%          HTTP handler Id to call if the user clicks the edit button.
 2356%
 2357%       @tbd    Translation of files to HREFS is a mess.  How to relate
 2358%               these elegantly?
 2359
 2360file(File) -->
 2361    file(File, []).
 2362
 2363file(File, Options) -->
 2364    { catch(nb_getval(pldoc_options, GenOptions), _, GenOptions = []),
 2365      merge_options(Options, GenOptions, FinalOptions)
 2366    },
 2367    link_file(File, FinalOptions),
 2368    !.
 2369file(File, Options) -->
 2370    { option(edit_handler(Handler), Options),
 2371      http_current_request(Request),
 2372      memberchk(path(Path), Request),
 2373      absolute_file_name(File, Location,
 2374			 [ relative_to(Path)
 2375			 ]),
 2376      http_link_to_id(Handler, [location(Location)], HREF),
 2377      format(atom(Title), 'Click to create ~w', [File])
 2378    },
 2379    html(a([href(HREF), class(nofile), title(Title)], File)).
 2380file(File, _) -->
 2381    html(code(class(nofile), File)).
 2382
 2383link_file(File, Options) -->
 2384    { file_href(File, HREF, Options),
 2385      option(label(Label), Options, File),
 2386      option(class(Class), Options, file)
 2387    },
 2388    html(a([class(Class), href(HREF)], Label)).
 2389
 2390%!  file_href(+FilePath, -HREF, +Options) is det.
 2391%
 2392%   Find URL for refering to FilePath based on Options.
 2393
 2394file_href(_, HREF, Options) :-
 2395    option(href(HREF), Options),
 2396    !.
 2397file_href(File, HREF, Options) :-
 2398    file_href_real(File, HREF0, Options),
 2399    map_extension(HREF0, HREF, Options).
 2400
 2401%!  map_extension(+HREFIn, -HREFOut, Options) is det.
 2402%
 2403%   Replace extension using the option
 2404%
 2405%       * map_extension(+Pairs)
 2406
 2407map_extension(HREF0, HREF, Options) :-
 2408    option(map_extension(Map), Options),
 2409    file_name_extension(Base, Old, HREF0),
 2410    memberchk(Old-New, Map),
 2411    !,
 2412    file_name_extension(Base, New, HREF).
 2413map_extension(HREF, HREF, _).
 2414
 2415
 2416file_href_real(File, HREF, Options) :-
 2417    (   option(absolute_path(Path), Options)
 2418    ;   existing_linked_file(File, Path)
 2419    ),
 2420    !,
 2421    (   option(files(Map), Options),
 2422	memberchk(file(Path, LinkFile), Map)
 2423    ->  true
 2424    ;   LinkFile = Path
 2425    ),
 2426    file_href(LinkFile, HREF).
 2427file_href_real(File, HREF, _) :-
 2428    directory_alias(Alias),
 2429    Term =.. [Alias,File],
 2430    absolute_file_name(Term, _,
 2431		       [ access(read),
 2432			 file_errors(fail)
 2433		       ]),
 2434    !,
 2435    http_absolute_location(Term, HREF, []).
 2436
 2437directory_alias(icons).
 2438directory_alias(css).
 2439
 2440
 2441%!  file_href(+FilePath, -HREF) is det.
 2442%
 2443%   Create a relative URL from  the   current  location to the given
 2444%   absolute file name. It resolves  the   filename  relative to the
 2445%   file being processed  that  is   available  through  the  global
 2446%   variable =pldoc_file=.
 2447
 2448file_href(Path, HREF) :-                % a loaded Prolog file
 2449    source_file(Path),
 2450    !,
 2451    doc_file_href(Path, HREF).
 2452file_href(Path, HREF) :-
 2453    (   nb_current(pldoc_output, CFile)
 2454    ;   nb_current(pldoc_file, CFile)
 2455    ),
 2456    CFile \== [],
 2457    !,
 2458    relative_file_name(Path, CFile, HREF).
 2459file_href(Path, Path).
 2460
 2461
 2462%!  existing_linked_file(+File, -Path) is semidet.
 2463%
 2464%   True if File is a path to an existing file relative to the
 2465%   current file.  Path is the absolute location of File.
 2466
 2467existing_linked_file(File, Path) :-
 2468    catch(b_getval(pldoc_file, CurrentFile), _, fail),
 2469    CurrentFile \== [],
 2470    absolute_file_name(File, Path,
 2471		       [ relative_to(CurrentFile),
 2472			 access(read),
 2473			 file_errors(fail)
 2474		       ]).
 2475
 2476
 2477%!  include(+FileName, +Type, +Options)// is det.
 2478%
 2479%   Inline FileName. If this is an image file, show an inline image.
 2480%   Else we create a link  like   file//1.  Called by \include(File,
 2481%   Type)  terms  in  the  DOM  term  generated  by  wiki.pl  if  it
 2482%   encounters [[file.ext]].
 2483
 2484include(PI, predicate, _) -->
 2485    !,
 2486    (   html_tokens_for_predicates(PI, [])
 2487    ->  []
 2488    ;   html(['[[', \predref(PI), ']]'])
 2489    ).
 2490include(File, image, Options) -->
 2491    { file_name_extension(_, svg, File),
 2492      file_href(File, HREF, Options),
 2493      !,
 2494      include(image_attribute, Options, Attrs0),
 2495      merge_options(Attrs0,
 2496		    [ alt(File),
 2497		      data(HREF),
 2498		      type('image/svg+xml')
 2499		    ], Attrs)
 2500    },
 2501    (   { option(caption(Caption), Options) }
 2502    ->  html(div(class(figure),
 2503		 [ div(class(image), object(Attrs, [])),
 2504		   div(class(caption), Caption)
 2505		 ]))
 2506    ;   html(object(Attrs, []))
 2507    ).
 2508include(File, image, Options) -->
 2509    { file_href(File, HREF, Options),
 2510      !,
 2511      include(image_attribute, Options, Attrs0),
 2512      merge_options(Attrs0,
 2513		    [ alt(File),
 2514		      border(0),
 2515		      src(HREF)
 2516		    ], Attrs)
 2517    },
 2518    (   { option(caption(Caption), Options) }
 2519    ->  html(div(class(figure),
 2520		 [ div(class(image), img(Attrs)),
 2521		   div(class(caption), Caption)
 2522		 ]))
 2523    ;   html(img(Attrs))
 2524    ).
 2525include(File, wiki, _Options) -->       % [[file.txt]] is included
 2526    { access_file(File, read),
 2527      !,
 2528      read_file_to_codes(File, String, []),
 2529      wiki_codes_to_dom(String, [], DOM)
 2530    },
 2531    html(DOM).
 2532include(File, _Type, Options) -->
 2533    link_file(File, Options),
 2534    !.
 2535include(File, _, _) -->
 2536    html(code(class(nofile), ['[[',File,']]'])).
 2537
 2538image_attribute(src(_)).
 2539image_attribute(alt(_)).
 2540image_attribute(title(_)).
 2541image_attribute(align(_)).
 2542image_attribute(width(_)).
 2543image_attribute(height(_)).
 2544image_attribute(border(_)).
 2545image_attribute(class(_)).
 2546image_attribute(style(_)).
 2547
 2548
 2549%!  html_tokens_for_predicates(+PI, +Options)// is semidet.
 2550%
 2551%   Inline description for a predicate as produced by the text below
 2552%   from wiki processing.
 2553%
 2554%   ==
 2555%           * [[member/2]]
 2556%           * [[append/3]]
 2557%   ==
 2558
 2559html_tokens_for_predicates([], _Options) -->
 2560    [].
 2561html_tokens_for_predicates([H|T], Options) -->
 2562    !,
 2563    html_tokens_for_predicates(H, Options),
 2564    html_tokens_for_predicates(T, Options).
 2565html_tokens_for_predicates(PI, Options) -->
 2566    { PI = _:_/_,
 2567      !,
 2568      (   doc_comment(PI, Pos, _Summary, Comment)
 2569      ->  true
 2570      ;   Comment = ''
 2571      )
 2572    },
 2573    object(PI, [Pos-Comment], [dl], _, Options).
 2574html_tokens_for_predicates(Spec, Options) -->
 2575    { findall(PI, documented_pi(Spec, PI), List),
 2576      List \== [], !
 2577    },
 2578    html_tokens_for_predicates(List, Options).
 2579html_tokens_for_predicates(Spec, Options) -->
 2580    man_page(Spec,
 2581	     [ links(false),                % no header
 2582	       navtree(false),              % no navigation tree
 2583	       footer(false),               % no footer
 2584	       synopsis(false)              % no synopsis
 2585	     | Options
 2586	     ]).
 2587
 2588
 2589documented_pi(Spec, PI) :-
 2590    generalise_spec(Spec, PI),
 2591    doc_comment(PI, _Pos, _Summary, _Comment).
 2592
 2593generalise_spec(Name/Arity, _M:Name/Arity).
 2594generalise_spec(Name//Arity, _M:Name//Arity).
 2595
 2596
 2597		 /*******************************
 2598		 *           WIKI FILES         *
 2599		 *******************************/
 2600
 2601
 2602%!  doc_for_wiki_file(+File, +Options) is det.
 2603%
 2604%   Write HTML for the File containing wiki data.
 2605
 2606doc_for_wiki_file(FileSpec, Options) :-
 2607    absolute_file_name(FileSpec, File,
 2608		       [ access(read)
 2609		       ]),
 2610    read_file_to_codes(File, String, []),
 2611    b_setval(pldoc_file, File),
 2612    call_cleanup(reply_wiki_page(File, String, Options),
 2613		 nb_delete(pldoc_file)).
 2614
 2615reply_wiki_page(File, String, Options) :-
 2616    wiki_codes_to_dom(String, [], DOM0),
 2617    title(DOM0, File, Title),
 2618    insert_edit_button(DOM0, File, DOM, Options),
 2619    reply_html_page(pldoc(wiki),
 2620		    title(Title),
 2621		    [ \html_requires(pldoc)
 2622		    | DOM
 2623		    ]).
 2624
 2625title(DOM, _, Title) :-
 2626    sub_term(h1(_,Title), DOM),
 2627    !.
 2628title(_, File, Title) :-
 2629    file_base_name(File, Title).
 2630
 2631insert_edit_button(DOM, _, DOM, Options) :-
 2632    option(edit(false), Options, false),
 2633    !.
 2634insert_edit_button([h1(Attrs,Title)|DOM], File,
 2635		   [h1(Attrs,[ span(style('float:right'),
 2636				   \edit_button(File, [edit(true)]))
 2637			     | Title
 2638			     ])|DOM], _) :- !.
 2639insert_edit_button(DOM, File,
 2640		   [ h1(class(wiki),
 2641			[ span(style('float:right'),
 2642			       \edit_button(File, [edit(true)]))
 2643			])
 2644		   | DOM
 2645		   ], _).
 2646
 2647
 2648		 /*******************************
 2649		 *            ANCHORS           *
 2650		 *******************************/
 2651
 2652%!  mode_anchor_name(+Mode, -Anchor:atom) is det.
 2653%
 2654%   Get the anchor name for a mode.
 2655
 2656mode_anchor_name(Var, _) :-
 2657    var(Var),
 2658    !,
 2659    instantiation_error(Var).
 2660mode_anchor_name(mode(Head, _), Anchor) :-
 2661    !,
 2662    mode_anchor_name(Head, Anchor).
 2663mode_anchor_name(Head is _Det, Anchor) :-
 2664    !,
 2665    mode_anchor_name(Head, Anchor).
 2666mode_anchor_name(Head, Anchor) :-
 2667    pred_anchor_name(Head, _, Anchor).
 2668
 2669
 2670%!  pred_anchor_name(+Head, -PI:atom/integer, -Anchor:atom) is det.
 2671%
 2672%   Create an HTML anchor name from Head.
 2673
 2674pred_anchor_name(//(Head), Name/Arity, Anchor) :-
 2675    !,
 2676    functor(Head, Name, DCGArity),
 2677    Arity is DCGArity+2,
 2678    format(atom(Anchor), '~w/~d', [Name, Arity]).
 2679pred_anchor_name(Head, Name/Arity, Anchor) :-
 2680    functor(Head, Name, Arity),
 2681    format(atom(Anchor), '~w/~d', [Name, Arity]).
 2682
 2683:- multifile prolog:message//1. 2684
 2685prolog:message(pldoc(module_comment_outside_module(Title))) -->
 2686    [ 'PlDoc comment <module> ~w does not appear in a module'-[Title] ]