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