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