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